by NYCU-Chung
A comprehensive statusline dashboard for Claude Code — session info, quota bars, agent tracker, MCP health, message history, and more.
# Add to your Claude Code skills
git clone https://github.com/NYCU-Chung/cc-statuslineGuides for using ai agents skills like cc-statusline.
Last scanned: 5/30/2026
{
"issues": [],
"status": "PASSED",
"scannedAt": "2026-05-30T15:21:52.997Z",
"npmAuditRan": true,
"pipAuditRan": true
}English · 繁體中文
A comprehensive statusline dashboard for Claude Code. See everything at a glance — no slash commands needed.

| Section | Info |
|---------|------|
| session summary | Auto-generated whole-session summary (Claude rewrites it every ~10 messages with built-in compression so it stays under ~120 chars) |
| directory | Current working directory + +added -removed lines |
| repo + branch | owner/repo (parsed from git remote) + branch + (N changed) |
| cost | cost $TOTAL (<window>) · $SESSION (this session) — all-session spend rendered as parallel parenthetical annotations. Window defaults to all time; set aggWindowDays in ~/.claude/cc-statusline-rows.json for a rolling view (e.g. 7 / 30 / 90). |
| model | Active model name + effort level with 5-tier color ladder (low dim / medium green / high yellow / xhigh orange / max red) |
| duration | Active session time — sum of every turn's wall-clock duration (UserPromptSubmit → Stop). Inter-turn idle is naturally excluded, no idle threshold needed. Shares the model row area visually but toggles independently (/cc-statusline:rows hide duration). |
| tokens / context / compact | tokens TOTAL (SESSION this session) (same all+session dual display as cost) · context window % · compact count (compact 1 time / compact N times) |
| 5h-quota | Color-coded bar (green → yellow → red) + auto-rolling resets Xh Ym countdown. Auto-zeros when resets_at passes real-world time (payload is stale until next message). |
| 7d-quota | Color-coded bar + auto-rolling resets Xd Yh countdown with same rollover behavior |
| agents | Subagents that ran in this session — critic ✓ 5m ago, parallel runs collapse to critic ○×3 (running) or critic ✓×2 5m ago (done) |
| memory | Which CLAUDE.md scopes are loaded (global / project / rules) |
| mcp | MCP server health probed via claude mcp list — count of active + each unhealthy server with its state (✘ failed, △ needs auth) |
| edited | Recently edited files in this session, newest first (long names front-truncated with …) |
| history | Right column showing the last messages (▶ you, ◀ Claude), grows to fill terminal width |
claude plugin marketplace add NYCU-Chung/cc-statusline
claude plugin install cc-statusline@cc-statusline
Hooks are registered automatically (via the plugin's own hooks/hooks.json), so you can skip the Hook wiring section below.
Then add the statusLine block to ~/.claude/settings.json — Claude Code doesn't allow plugins to set this for you:
{
"statusLine": {
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/statusline.js",
"refreshInterval": 30
}
}
Pick the block that matches your shell. ~ is expanded by bash/zsh before git sees it, but PowerShell and cmd don't expand it — using ~ there makes git clone create a literal ~ folder (reported in #6). Use $HOME / %USERPROFILE% instead.
bash / zsh / Git Bash on Windows
git clone https://github.com/NYCU-Chung/cc-statusline ~/cc-statusline
mkdir -p ~/.claude/hooks
cp ~/cc-statusline/statusline.js ~/.claude/statusline.js
cp ~/cc-statusline/hooks/*.js ~/.claude/hooks/
PowerShell
git clone https://github.com/NYCU-Chung/cc-statusline "$HOME/cc-statusline"
New-Item -ItemType Directory -Force "$HOME/.claude/hooks" | Out-Null
Copy-Item "$HOME/cc-statusline/statusline.js" "$HOME/.claude/statusline.js"
Copy-Item "$HOME/cc-statusline/hooks/*.js" "$HOME/.claude/hooks/"
Windows cmd
git clone https://github.com/NYCU-Chung/cc-statusline "%USERPROFILE%\cc-statusline"
mkdir "%USERPROFILE%\.claude\hooks" 2>nul
copy "%USERPROFILE%\cc-statusline\statusline.js" "%USERPROFILE%\.claude\statusline.js"
copy "%USERPROFILE%\cc-statusline\hooks\*.js" "%USERPROFILE%\.claude\hooks\"
Then add this statusLine block to ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "node ~/.claude/statusline.js",
"refreshInterval": 30
}
}
Add these to your ~/.claude/settings.json hooks section to enable all statusline features:
{
"hooks": {
"SubagentStart": [{ "matcher": ".*", "hooks": [{ "type": "command", "command": "node ~/.claude/hooks/subagent-tracker.js" }] }],
"SubagentStop": [{ "matcher": ".*", "hooks": [{ "type": "command", "command": "node ~/.claude/hooks/subagent-tracker.js" }] }],
"PreCompact": [{ "matcher": ".*", "hooks": [{ "type": "command", "command": "node ~/.claude/hooks/compact-monitor.js" }] }],
"UserPromptSubmit": [{ "hooks": [
{ "type": "command", "command": "node ~/.claude/hooks/message-tracker.js" },
{ "type": "command", "command": "node ~/.claude/hooks/summary-updater.js" },
{ "type": "command", "command": "node ~/.claude/hooks/active-time-tracker.js" }
]}],
"Stop": [{ "matcher": ".*", "hooks": [
{ "type": "command", "command": "node ~/.claude/hooks/message-tracker.js" },
{ "type": "command", "command": "node ~/.claude/hooks/active-time-tracker.js" }
]}],
"PostToolUse": [{ "matcher": "Write|Edit", "hooks": [
{ "type": "command", "command": "node ~/.claude/hooks/file-tracker.js" }
]}]
}
}
| Hook | Event | Purpose |
|------|-------|---------|
| subagent-tracker.js | SubagentStart / SubagentStop | Tracks which agents are running or finished, including concurrent invocations |
| compact-monitor.js | PreCompact | Counts how many times context was compacted |
| file-tracker.js | PostToolUse (Write/Edit) | Records recently edited files |
| message-tracker.js | UserPromptSubmit / Stop | Caches recent messages for the history column |
| summary-updater.js | UserPromptSubmit | Every ~10 messages, asks Claude to rewrite the whole-session summary with compression rules |
| active-time-tracker.js | UserPromptSubmit / Stop | Maintains active session time (sum of turn durations) — bootstraps from transcript on first run, then accumulates per turn |
| mcp-status-refresh.js | (not a Claude Code hook event) | Background script auto-spawned by statusline.js when the MCP cache is stale. Probes claude mcp list and writes ~/.claude/mcp-status-cache.json. Lives in hooks/ only because that's where the install steps copy it; it's never invoked via the hooks settings entries. |
| mcp-status-refresh.js | (none — auto-spawned) | Statusline launches this in the background each render to refresh ~/.claude/mcp-status-cache.json from claude mcp list. Self-skips if cache is fresh (<90s). |
Delta-based cost / lines / tokens. Claude Code occasionally resets cost.total_cost_usd etc. mid-session (context compaction, auto-recovery, etc.). The statusline tracks deltas in ~/.claude/cc-statusline/cum-<sid>.json — when the payload value drops, only the baseline is reset; the cumulative total never goes backward. (Active session time follows a separate path — see "Per-feature state isolation" below.)
Defensive per-session keying via transcript filename. Every per-session tmp file (cum, messages, summary, agents, files, compact count) is keyed by path.basename(transcript_path) rather than the runtime session_id payload, falling back to session_id only when no transcript is present. The transcript filename is the canonical UUID of the logical session and is invariant for its lifetime, so this keying stays correct even if session_id semantics ever shift. (Empirically on the current Claude Code build, session_id and the transcript filename UUID are already identical — the choice is preventive, not a bug fix.)
Active session time, hook-driven. The duration row is the sum of (Stop timestamp − UserPromptSubmit timestamp) for every turn, maintained by hooks/active-time-tracker.js. The first run on a session bootstraps from the transcript JSONL by replaying user→assistant timestamp pairs. Because every slice is bounded by an open turn, idle time outside turns is naturally excluded — no threshold, no heuristic.
Persistent state lives under ~/.claude/cc-statusline/, not tmpdir. All per-session accumulation files (cum, active, summary, msgs, msgcount, agents, files, compacts) sit under a dedicated dir in the user's ~/.claude/ rather than os.tmpdir(). The OS treats tmpdir as throwaway — Windows Storage Sense clears it on a 30-day cycle, cleanmgr / antivirus on whim, /tmp resets on Linux reboot — which was the root cause behind every cost-loss and active-time-reset story. Only true ephemeral caches (resolved terminal width, .tmp rename staging) belong in tmpdir. Existing tmpdir files are migrated automatically on the first render after upgrade.
Per-feature state isolation (cost-loss fix). Active session time lives in its own state file (active-<sid>.json), independent from the cum file (cum-<sid>.json) that tracks cost / lines / tokens. The cum file is owned exclusively by statusline.js — no hook ever writes to it. This invariant matters because earlier versions had hooks that wrote partial cum files (containing only their own fields), which made the next statusline render's fallback path reset accumulated cost.total to 0. Splitting state per writer eliminates the failure mode entirely; the cum read path was also hardened so a missing field never resets total.
Width is user-set, not auto-detected. Width auto-detection inside the statusline hook is essentially impossible: stdio is a pipe so process.stdout.columns is undefined, $COLUMNS is unset,
No comments yet. Be the first to share your thoughts!