A desktop companion app that monitors your Claude Code sessions in real time. Built with Electron.
CC Companion is a lightweight always-on-top window that sits at the top of your screen, tracking every Claude Code instance running on your machine — which project it's in, whether it's actively working or idle, CPU and memory usage, token consumption, conversation turns, and how long it's been in each state.
demo.mp4
| Light mode | Working state | Settings + detail |
| Dark mode with detail panel | Session history |
- Auto-detects all running Claude Code processes (filters out Claude desktop app and subagent processes)
- Per-instance tile showing project name, model, status, and context usage
- Session analytics: turn count, input/output token usage, context usage, model name — read directly from Claude's session files
- Session timing: start time (with date if not today) and total elapsed time shown in detail panel
- Anti-flicker: 3-second idle grace period prevents flickering between working/ready during brief pauses between tool calls
- Smart timer reset: timer resets when a new user turn starts, so quick back-and-forth exchanges get fresh timers
- Instances display in a 2-column grid, scrollable when more than 6 are running
- Rename instances — right-click any tile to give it a custom name (useful when running multiple instances in the same project folder)
- Close instances — right-click a tile and confirm to terminate a Claude Code session (terminal tab stays open)
- Click any instance tile to focus its terminal window — works across Terminal.app, iTerm2, Ghostty, WezTerm, kitty, Cursor, and VS Code (see Terminal support)
- Drag tiles to reorder them
- History panel (⏳) — browse your last 50 Claude Code sessions across all projects
- Each entry shows project name, first message, turn count, and relative time
- Resume button — opens a new Terminal tab with
claude --resumein the correct project directory - Reads directly from
~/.claude/history.jsonl— no external services
Gear button opens a floating popup with all settings (persisted across restarts):
- Theme — toggle between Dark and Light mode
- Show Work Timer — display elapsed working time on active instance tiles
- Show Ready Timer — display elapsed idle time on ready instance tiles
- Opacity — background transparency (Light 80% / Mid 90% / Full 100%) — only affects background, not text
Click the ⓘ button on any tile to view detailed stats:
- Session start time and total elapsed
- Model and git branch
- Context usage percentage
- Input/output/cached token counts
- CPU and memory usage
- Working directory path
- Settings (⚙) — open settings panel
- History (⏳) — browse and resume past sessions
- Center (⊙) — snap the island to center-top of screen
- Minimize (−) — minimize to dock (native macOS animation)
- Quit (✕) — exit the app
- Tooltips — hover any button to see its function
git clone https://github.com/jiahongc/cc-companion.git
cd cc-companion
npm install
npm startRequires Node.js v18+.
A pre-built DMG is available on the Releases page. Since the app is unsigned, macOS will block it on first launch. After dragging to Applications, run:
xattr -cr /Applications/CC\ Companion.app
cc-companion/
├── electron/
│ ├── main.js # Electron main process, IPC handlers, window management
│ ├── preload.js # Context bridge API for renderer
│ └── watcher.js # Claude Code process detection, session analytics
├── src/
│ ├── compact.html # Dynamic Island window
│ ├── compact.css # Dynamic Island styles
│ └── compact.js # Dynamic Island renderer
├── test/
│ └── watcher.test.js # 66 tests covering detection, state, tokens, formatting
├── assets/
│ ├── icon_1024.png # App icon (1024x1024 source)
│ ├── icon.icns # macOS app icon
│ └── iconTemplate.png # Tray icon
└── package.json
The watcher polls ps every 2 seconds to find Claude CLI processes (case-insensitive), filtering out the Claude desktop app, helper processes, subagent child processes, and Electron/system binaries. It resolves each process's working directory via lsof -d cwd to get the project name. Async instance initialization is guarded against duplicate creation during the discovery window.
Activity detection uses a multi-signal approach with tiered staleness:
-
JSONL state (primary) — reads the last entry from Claude's session JSONL to determine ground truth. Some entries are immediately idle (
end_turn,system,file-history-snapshot). Active entries get entry-type-specific staleness thresholds:Entry Staleness Rationale assistant(null)10s Streaming is continuous; 10s silence = interrupted assistant(tool_use)5 min Tools (builds, browser) run long without writes progress5 min Subagents run long without writes user2 min Claude should start responding within 2 min queue-operation30s Quick task notifications result30s Tool output; Claude should pick up quickly -
CPU fallback — beyond any staleness threshold, if CPU >= 5%, the instance is still treated as active. Also used when no JSONL file exists yet (brand new process).
-
Idle grace period — when transitioning from active to idle, the watcher waits 3 seconds (2 consecutive polls) before confirming the transition. This prevents UI flickering during brief pauses between tool calls or multi-step responses.
-
Turn-aware timer reset — the working timer resets when a new user turn is detected (turn count increases on idle→active transition), so quick exchanges get fresh timers instead of accumulating from the original session start.
State transitions (active → idle, idle → active) are timestamped for duration tracking.
For each detected instance, the watcher reads:
~/.claude/sessions/{pid}.json— session ID and start time~/.claude/projects/{project-key}/{session-id}.jsonl— conversation log
From the JSONL it extracts:
- Turn count — real user prompts (excludes tool-use results, auto-compact summaries, and meta/system-injected messages)
- Token usage — input, output, cache read, cache creation tokens
- Context tokens — current context window fill (input + cache read + cache creation from last entry)
- Model — which Claude model is active (Fable 5, Opus 4.8, Opus 4.7, Opus 4.6, Sonnet 4.6, Haiku 4.5, and earlier)
- Git branch — current branch name
The context-usage bar denominator adapts to the model and whether the 1M-token beta is active:
| Model | Default | With 1M beta |
|---|---|---|
| Fable 5, Opus 4.8, Opus 4.7, Opus 4.6, Sonnet 4.6 | 200k | 1M |
| Opus 4.5 / 4.1 / 4, Sonnet 4.5 / 4, all Haiku | 200k | 200k (no beta support) |
The 1M beta is detected from two signals:
CLAUDE_CODE_ENABLE_1M_CONTEXT=1in the Claude process env (set via shell orsettings.json'senvblock) — sniffed once viaps ewwat instance init.- Observed context usage exceeding 200k — catches the
/model claude-opus-4-7[1m]runtime toggle, which isn't persisted to env or disk.
Stats refresh every 5 seconds. Only emits updates when data actually changes (deduplicated via snapshot key).
Clicking an instance tile brings the hosting terminal tab to the front. The best focus mechanism depends on what the terminal exposes:
| Terminal | Mechanism | Notes |
|---|---|---|
| Terminal.app | Native AppleScript, TTY match | Works out of the box |
| iTerm2 | Native AppleScript, TTY match | Works out of the box |
| Ghostty | Native AppleScript, CWD match | Works out of the box |
| WezTerm | wezterm cli activate-pane, CWD match |
Works out of the box — uses the wezterm CLI |
| kitty | kitty @ focus-tab, CWD match |
Requires allow_remote_control yes and a listen_on socket in kitty.conf |
| Cursor / VS Code | System Events, window-title match | Works because these set their window title to the project name |
| Warp, Alacritty, Hyper, Rio, Tabby | System Events fallback | Best-effort — will at least bring the app to the front |
npm test # run all 66 tests
npm run test:watch # watch modeTests cover activity detection, state transitions, idle grace period, timer resets, session reset on /clear, model switching, token counting, snapshot deduplication, duplicate prevention, and formatting helpers.
To package as a standalone .dmg:
npm run build:macOutput goes to the dist/ folder.
- Local only — CC Companion runs entirely on your machine. No data is sent to any server, no network requests are made, no telemetry or analytics.
- Read-only — The app only reads Claude Code's session files (
~/.claude/sessions/,~/.claude/projects/). It never writes to them or modifies your Claude sessions in any way. - No secrets — The app does not access, store, or transmit API keys, tokens, or credentials. It reads process metadata (
ps) and session JSONL files, which contain conversation structure but not your API keys. - Process isolation — Electron runs with
contextIsolation: trueandnodeIntegration: false. The renderer communicates with the main process only through a restricted preload API. - Open source — Run from source so you can verify the code yourself before running it.
- Fork this repo
- Create a feature branch (
git checkout -b feature/my-feature) - Commit your changes (
git commit -am 'Add my feature') - Push to the branch (
git push origin feature/my-feature) - Open a Pull Request
MIT