3D observability app for AI coding agents. Electron desktop app that embeds real terminal sessions and visualizes Claude Code agents as voxel characters working in an isometric office.
pnpm install
pnpm dev # launches Electron with hot-reload
pnpm build # production build → out/Electron app with three process layers:
src/main/ → Electron main process (node-pty terminals, IPC)
src/preload/ → Context bridge (electronAPI)
src/renderer/ → React + Three.js UI (Vite-bundled)
index.ts— Window creation, lifecycleterminal.ts— PTY sessions via node-pty, Claude detection polling (1s interval), IPC handlers
components/ → Terminal UI (TerminalPanel, TerminalTab)
scene/ → R3F 3D scene (Office, Desk, AgentCharacter, Room, Lighting, OfficeCat)
scene/effects/ → Status effects (ThoughtBubble, DeskFire, Papers, Confetti, OfficePlant)
hud/ → 2D overlay (HUD, AgentCard, StatsBar, Toast)
services/ → ClaudeDetector (regex-based terminal output parser)
store/ → Zustand store (agents.ts — agents, terminals, toasts)
types.ts → All shared types (Agent, AgentStatus, CelebrationType, etc.)
- Terminal → PTY: User types in xterm.js →
terminal:writeIPC → node-pty - PTY → Terminal + Detection: node-pty output →
terminal:dataIPC → xterm.js render +ClaudeDetector.feed() - Claude Detection: Main process polls foreground process name + scans output patterns →
terminal:claude-statusIPC - Agent Lifecycle: Claude starts →
addAgent()spawns 3D character at desk. Claude exits →removeAgent(). - Activity Tracking:
ClaudeDetectorextracts status, tokens, file edits, commits from terminal output →updateAgent()in store - Celebrations: Git commit detected → confetti burst (4s) + persistent desk plant (max 5) + toast
Agents live in the Zustand store (store/agents.ts). Key fields:
| Field | Source | Notes |
|---|---|---|
status |
ClaudeDetector | thinking/streaming/tool_calling/error/done |
tokens_input/output |
ClaudeDetector | Cumulative, set directly from CLI output |
files_modified |
ClaudeDetector | Incremented on Wrote/Created/Updated/Edited |
commitCount |
ClaudeDetector | Drives plant count (max 5 rendered) |
activeCelebration |
TerminalTab | Set to 'confetti', auto-cleared after 4s |
appearance |
Random on spawn | Shirt, hair, skin, pants, gender — voxel style |
The detector (services/claudeDetector.ts) matches terminal output against regex patterns:
- Status: spinner chars, box-drawing, tool names, error/done markers
- Tokens:
N input ... N outputorTokens: N ... N - Files:
Wrote|Created|Updated|Edited <filename> - Commits:
[branch hash](git commit success line)
Each agent status maps to a character animation + optional desk effect:
| Status | Animation | Effect |
|---|---|---|
| thinking | Pacing, bouncing | ThoughtBubble |
| streaming | Seated, rapid typing | Papers |
| tool_calling | Walking between points | — |
| error | Shaking, arms up | DeskFire |
| done | Arms raised victory | Green glow |
| (commit) | — | Confetti burst + OfficePlant |
- pnpm as package manager
- TypeScript strict, no
any - Functional React with hooks, no classes
- Zustand for state — direct
getState()in callbacks, hooks in components - R3F for 3D — instanced meshes for particle effects,
useFramefor animation - Tailwind v4 for 2D styling
- Effects are self-contained in
scene/effects/— each takes apositionprop - Agent appearance is randomized on spawn, not user-configurable
- Main process detection is dual: process name polling + output pattern matching
pnpm package # electron-vite build + electron-builder --mac --dir
pnpm package:dmg # electron-vite build + electron-builder --mac dmg- Output lands in
release/— DMG, blockmap, andlatest-mac.yml(for electron-updater) - Mac config uses
"identity": null— skips code signing (no Apple Developer account needed) - Users must right-click > Open on first launch to bypass Gatekeeper
- Bump
versioninpackage.json pnpm package:dmglocally (CI doesn't build DMGs)- PR → merge to main
gh release create vX.Y.Z release/agent-observer-*.dmg release/*.blockmap release/latest-mac.yml --target main- The
latest-mac.ymlis required for electron-updater auto-update detection
- Branch protection is strict — can't push to main directly or admin-merge past failed checks
- npm audit endpoint can go down (500 errors); CI audit step handles this gracefully with
ERR_PNPM_AUDIT_BAD_RESPONSEdetection - Transitive dep vulnerabilities (e.g.
tar,minimatchin electron toolchain) — fix viapnpm.overridesinpackage.json, not by upgrading direct deps - GitHub release tags: when recreating a release, use
--target mainif the tag already exists locally
| Channel | Direction | Purpose |
|---|---|---|
terminal:create |
renderer → main | Spawn PTY, returns { id } |
terminal:write |
renderer → main | Send keystrokes to PTY |
terminal:resize |
renderer → main | Sync xterm dimensions |
terminal:kill |
renderer → main | Destroy PTY session |
terminal:data |
main → renderer | PTY output chunks |
terminal:exit |
main → renderer | PTY process exited |
terminal:claude-status |
main → renderer | Claude running state change |