How It Works
c9watch is a native macOS desktop app built with Tauri (Rust + Svelte). It monitors Claude Code sessions by reading local files and scanning OS processes — no network calls, no API keys, no telemetry.
Architecture overview
Section titled “Architecture overview”The app has two layers: a Rust backend that handles process scanning, file parsing, and system integration, and a Svelte frontend that reactively renders the UI. They communicate via Tauri’s IPC bridge (commands and events).
Project structure
Section titled “Project structure”c9watch/├── src/ # SvelteKit frontend│ ├── routes/│ │ ├── (app)/ # Main dashboard (monitor, history, cost tabs)│ │ └── popover/ # Tray popover window│ ├── lib/│ │ ├── components/ # Svelte components│ │ ├── stores/ # Reactive state management│ │ ├── demo/ # Demo mode with mock data│ │ ├── api.ts # Tauri command wrappers│ │ └── types.ts # TypeScript type definitions│ └── app.css # Global styles (Vercel Noir theme)├── src-tauri/ # Rust backend (Tauri)│ └── src/│ ├── lib.rs # App setup, tray icon, NSPanel popover│ ├── polling.rs # Background session detection loop│ ├── actions.rs # Stop/open session, IDE detection│ ├── web_server.rs # WebSocket server for mobile clients│ ├── auth.rs # Token generation, local IP discovery│ └── session/│ ├── parser.rs # JSONL file parsing and message extraction│ ├── detector.rs # Process-to-session matching│ ├── status.rs # Status determination from JSONL entries│ ├── history.rs # Session history index and deep search│ ├── cost.rs # Cost aggregation with mtime caching│ ├── permissions.rs # Auto-approval rule checking│ └── custom_names.rs # User-defined session titlesLive monitoring
Section titled “Live monitoring”A background thread runs a polling loop every 2 seconds:
- Process scan — Uses the sysinfo crate to find all running
claudeprocesses - Path matching — Each process’s working directory is encoded and matched to session files in
~/.claude/projects/ - JSONL parsing — The last N entries of each session’s JSONL file are parsed to extract the current conversation state
- Status determination — Based on the latest messages:
- Working — An assistant message is being streamed, or a tool is executing
- Needs Permission — A tool call is pending with
requires_approval: true - Idle — The last message is from the assistant and no tool is pending
- Event push — Status updates are emitted as Tauri events, which the Svelte frontend subscribes to reactively
The UI sorts sessions by priority: permission requests surface first, then working sessions, then idle ones. Each session card shows metadata extracted from the JSONL: model name, project path, git branch, current tool, and elapsed time.
IDE detection
Section titled “IDE detection”When you click “Open” on a session, c9watch needs to find the parent terminal or IDE. It does this by:
- Walking up the process tree from the
claudeprocess to find the parent application - Matching the parent binary name against a known list of terminals and IDEs
- For JetBrains IDEs, checking three resolution tiers: Toolbox scripts directory, user Applications, and system Applications
Currently supported: VS Code, Zed, iTerm2, Ghostty, tmux, Terminal.app, Antigravity, and 15 JetBrains IDEs (IntelliJ IDEA, PhpStorm, WebStorm, PyCharm, GoLand, CLion, Rider, RubyMine, DataGrip, Android Studio, Aqua, Fleet, RustRover, and their CE variants).
Session history
Section titled “Session history”The history feature works in two stages:
- Index loading — Reads
~/.claude/history.jsonlto get the list of all past sessions with their metadata (project, title, timestamp) - Deep search — When you type a search query, individual session JSONL files are scanned across all project directories for matching content. This is debounced to avoid excessive I/O.
Search results include context snippets with keyword highlighting. Clicking a result opens the full conversation viewer and scrolls to the matching message with a highlight animation.
Cost tracking
Section titled “Cost tracking”The cost tracker parses assistant message metadata from JSONL files to extract:
- Model name (claude-3.5-sonnet, claude-3-opus, claude-3-haiku, etc.)
- Input and output token counts
- Cache read/write token counts
Costs are computed using a per-model pricing table maintained in session/cost.rs. To avoid re-scanning unchanged files on every dashboard open, results are cached by file mtime — only modified files are re-parsed.
Tray popover
Section titled “Tray popover”The menu bar popover uses a native macOS NSPanel (not a web view) configured with:
NSWindowStyleMask.nonactivatingPanel— doesn’t steal focus from your terminalNSWindowLevel.popUpMenu— appears above full-screen apps- Custom positioning relative to the tray icon
The popover content is a separate Svelte route (/popover) loaded in a second Tauri webview, with its own optimized layout for the smaller window size.
Mobile / Web client
Section titled “Mobile / Web client”c9watch includes a WebSocket server (powered by tokio-tungstenite) that broadcasts session updates to connected clients. When you scan the QR code:
- The app detects your local IP address
- Generates a one-time auth token
- Encodes both into a QR code URL
- Your phone’s browser connects via WebSocket and receives real-time updates
Tech stack
Section titled “Tech stack”| Layer | Technology |
|---|---|
| Desktop framework | Tauri 2 |
| Frontend | SvelteKit + Svelte 5 |
| Backend | Rust |
| Process discovery | sysinfo |
| WebSocket server | tokio-tungstenite |
| Design system | Vercel Noir (true black, Geist fonts) |