[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-1358":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":13,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":13,"stars7d":15,"stars30d":16,"stars90d":14,"forks30d":14,"starsTrendScore":15,"compositeScore":17,"rankGlobal":9,"rankLanguage":9,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":21,"hasPages":19,"topics":22,"createdAt":9,"pushedAt":9,"updatedAt":32,"readmeContent":33,"aiSummary":34,"trendingCount":14,"starSnapshotCount":14,"syncStatus":35,"lastSyncTime":36,"discoverSource":37},1358,"cc-statusline","NYCU-Chung\u002Fcc-statusline","NYCU-Chung","A comprehensive statusline dashboard for Claude Code — session info, quota bars, agent tracker, MCP health, message history, and more.",null,"JavaScript",254,30,1,0,3,8,48.77,"MIT License",false,"main",true,[23,24,25,26,27,28,29,30,31],"claude-code","cli","dashboard","developer-tools","hooks","monitoring","statusline","terminal","unicode","2026-06-12 04:00:09","# cc-statusline\n\n**English · [繁體中文](.\u002FREADME.zh-TW.md)**\n\nA comprehensive statusline dashboard for Claude Code. See everything at a glance — no slash commands needed.\n\n![Demo](.\u002Fimages\u002Fdemo-en.png)\n\n## What it shows\n\n| Section | Info |\n|---------|------|\n| **session summary** | Auto-generated whole-session summary (Claude rewrites it every ~10 messages with built-in compression so it stays under ~120 chars) |\n| **directory** | Current working directory + `+added -removed lines` |\n| **repo + branch** | `owner\u002Frepo` (parsed from `git remote`) + branch + `(N changed)` |\n| **cost** | `cost $TOTAL (\u003Cwindow>) · $SESSION (this session)` — all-session spend rendered as parallel parenthetical annotations. Window defaults to all time; set `aggWindowDays` in `~\u002F.claude\u002Fcc-statusline-rows.json` for a rolling view (e.g. `7` \u002F `30` \u002F `90`). |\n| **model** | Active model name + effort level with 5-tier color ladder (`low` dim \u002F `medium` green \u002F `high` yellow \u002F `xhigh` orange \u002F `max` red) |\n| **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 (`\u002Fcc-statusline:rows hide duration`). |\n| **tokens \u002F context \u002F compact** | `tokens TOTAL (SESSION this session)` (same all+session dual display as cost) · context window % · compact count (`compact 1 time` \u002F `compact N times`) |\n| **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). |\n| **7d-quota** | Color-coded bar + auto-rolling `resets Xd Yh` countdown with same rollover behavior |\n| **agents** | Subagents that ran in this session — `critic ✓ 5m ago`, parallel runs collapse to `critic ○×3` (running) or `critic ✓×2 5m ago` (done) |\n| **memory** | Which CLAUDE.md scopes are loaded (global \u002F project \u002F rules) |\n| **mcp** | MCP server health probed via `claude mcp list` — count of active + each unhealthy server with its state (`✘ failed`, `△ needs auth`) |\n| **edited** | Recently edited files in this session, newest first (long names front-truncated with `…`) |\n| **history** | Right column showing the last messages (▶ you, ◀ Claude), grows to fill terminal width |\n\n## Install\n\n### Option A — plugin install (recommended)\n\n```\nclaude plugin marketplace add NYCU-Chung\u002Fcc-statusline\nclaude plugin install cc-statusline@cc-statusline\n```\n\nHooks are registered automatically (via the plugin's own `hooks\u002Fhooks.json`), so you can **skip the Hook wiring section below**.\n\nThen add the `statusLine` block to `~\u002F.claude\u002Fsettings.json` — Claude Code doesn't allow plugins to set this for you:\n\n```json\n{\n  \"statusLine\": {\n    \"type\": \"command\",\n    \"command\": \"node ${CLAUDE_PLUGIN_ROOT}\u002Fstatusline.js\",\n    \"refreshInterval\": 30\n  }\n}\n```\n\n### Option B — manual install (for hacking on the script)\n\nPick the block that matches your shell. **`~` is expanded by bash\u002Fzsh before `git` sees it, but PowerShell and cmd don't expand it** — using `~` there makes `git clone` create a literal `~` folder (reported in [#6](https:\u002F\u002Fgithub.com\u002FNYCU-Chung\u002Fcc-statusline\u002Fissues\u002F6)). Use `$HOME` \u002F `%USERPROFILE%` instead.\n\n**bash \u002F zsh \u002F Git Bash on Windows**\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002FNYCU-Chung\u002Fcc-statusline ~\u002Fcc-statusline\nmkdir -p ~\u002F.claude\u002Fhooks\ncp ~\u002Fcc-statusline\u002Fstatusline.js ~\u002F.claude\u002Fstatusline.js\ncp ~\u002Fcc-statusline\u002Fhooks\u002F*.js ~\u002F.claude\u002Fhooks\u002F\n```\n\n**PowerShell**\n```powershell\ngit clone https:\u002F\u002Fgithub.com\u002FNYCU-Chung\u002Fcc-statusline \"$HOME\u002Fcc-statusline\"\nNew-Item -ItemType Directory -Force \"$HOME\u002F.claude\u002Fhooks\" | Out-Null\nCopy-Item \"$HOME\u002Fcc-statusline\u002Fstatusline.js\" \"$HOME\u002F.claude\u002Fstatusline.js\"\nCopy-Item \"$HOME\u002Fcc-statusline\u002Fhooks\u002F*.js\" \"$HOME\u002F.claude\u002Fhooks\u002F\"\n```\n\n**Windows cmd**\n```cmd\ngit clone https:\u002F\u002Fgithub.com\u002FNYCU-Chung\u002Fcc-statusline \"%USERPROFILE%\\cc-statusline\"\nmkdir \"%USERPROFILE%\\.claude\\hooks\" 2>nul\ncopy \"%USERPROFILE%\\cc-statusline\\statusline.js\" \"%USERPROFILE%\\.claude\\statusline.js\"\ncopy \"%USERPROFILE%\\cc-statusline\\hooks\\*.js\" \"%USERPROFILE%\\.claude\\hooks\\\"\n```\n\nThen add this `statusLine` block to `~\u002F.claude\u002Fsettings.json`:\n\n```json\n{\n  \"statusLine\": {\n    \"type\": \"command\",\n    \"command\": \"node ~\u002F.claude\u002Fstatusline.js\",\n    \"refreshInterval\": 30\n  }\n}\n```\n\n### Hook wiring (Option B only — plugin install does this automatically)\n\nAdd these to your `~\u002F.claude\u002Fsettings.json` hooks section to enable all statusline features:\n\n```json\n{\n  \"hooks\": {\n    \"SubagentStart\": [{ \"matcher\": \".*\", \"hooks\": [{ \"type\": \"command\", \"command\": \"node ~\u002F.claude\u002Fhooks\u002Fsubagent-tracker.js\" }] }],\n    \"SubagentStop\": [{ \"matcher\": \".*\", \"hooks\": [{ \"type\": \"command\", \"command\": \"node ~\u002F.claude\u002Fhooks\u002Fsubagent-tracker.js\" }] }],\n    \"PreCompact\": [{ \"matcher\": \".*\", \"hooks\": [{ \"type\": \"command\", \"command\": \"node ~\u002F.claude\u002Fhooks\u002Fcompact-monitor.js\" }] }],\n    \"UserPromptSubmit\": [{ \"hooks\": [\n      { \"type\": \"command\", \"command\": \"node ~\u002F.claude\u002Fhooks\u002Fmessage-tracker.js\" },\n      { \"type\": \"command\", \"command\": \"node ~\u002F.claude\u002Fhooks\u002Fsummary-updater.js\" },\n      { \"type\": \"command\", \"command\": \"node ~\u002F.claude\u002Fhooks\u002Factive-time-tracker.js\" }\n    ]}],\n    \"Stop\": [{ \"matcher\": \".*\", \"hooks\": [\n      { \"type\": \"command\", \"command\": \"node ~\u002F.claude\u002Fhooks\u002Fmessage-tracker.js\" },\n      { \"type\": \"command\", \"command\": \"node ~\u002F.claude\u002Fhooks\u002Factive-time-tracker.js\" }\n    ]}],\n    \"PostToolUse\": [{ \"matcher\": \"Write|Edit\", \"hooks\": [\n      { \"type\": \"command\", \"command\": \"node ~\u002F.claude\u002Fhooks\u002Ffile-tracker.js\" }\n    ]}]\n  }\n}\n```\n\n## What each hook does\n\n| Hook | Event | Purpose |\n|------|-------|---------|\n| `subagent-tracker.js` | SubagentStart \u002F SubagentStop | Tracks which agents are running or finished, including concurrent invocations |\n| `compact-monitor.js` | PreCompact | Counts how many times context was compacted |\n| `file-tracker.js` | PostToolUse (Write\u002FEdit) | Records recently edited files |\n| `message-tracker.js` | UserPromptSubmit \u002F Stop | Caches recent messages for the history column |\n| `summary-updater.js` | UserPromptSubmit | Every ~10 messages, asks Claude to rewrite the whole-session summary with compression rules |\n| `active-time-tracker.js` | UserPromptSubmit \u002F Stop | Maintains active session time (sum of turn durations) — bootstraps from transcript on first run, then accumulates per turn |\n| `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 `~\u002F.claude\u002Fmcp-status-cache.json`. Lives in `hooks\u002F` only because that's where the install steps copy it; it's never invoked via the hooks settings entries. |\n| `mcp-status-refresh.js` | (none — auto-spawned) | Statusline launches this in the background each render to refresh `~\u002F.claude\u002Fmcp-status-cache.json` from `claude mcp list`. Self-skips if cache is fresh (\u003C90s). |\n\n## How it survives resets and multi-session\n\n**Delta-based cost \u002F lines \u002F tokens.** Claude Code occasionally resets `cost.total_cost_usd` etc. mid-session (context compaction, auto-recovery, etc.). The statusline tracks deltas in `~\u002F.claude\u002Fcc-statusline\u002Fcum-\u003Csid>.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.)\n\n**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.)\n\n**Active session time, hook-driven.** The `duration` row is the sum of (`Stop` timestamp − `UserPromptSubmit` timestamp) for every turn, maintained by `hooks\u002Factive-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.\n\n**Persistent state lives under `~\u002F.claude\u002Fcc-statusline\u002F`, not `tmpdir`.** All per-session accumulation files (`cum`, `active`, `summary`, `msgs`, `msgcount`, `agents`, `files`, `compacts`) sit under a dedicated dir in the user's `~\u002F.claude\u002F` rather than `os.tmpdir()`. The OS treats tmpdir as throwaway — Windows Storage Sense clears it on a 30-day cycle, `cleanmgr` \u002F antivirus on whim, `\u002Ftmp` 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.\n\n**Per-feature state isolation (cost-loss fix).** Active session time lives in its own state file (`active-\u003Csid>.json`), independent from the cum file (`cum-\u003Csid>.json`) that tracks cost \u002F lines \u002F 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`.\n\n**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, `tput cols` returns 80, PowerShell-spawned subprocesses report their own hidden-window width, and `\u002Fdev\u002Ftty` is not accessible. The upstream request to expose the actual width was [closed as not planned](https:\u002F\u002Fgithub.com\u002Fanthropics\u002Fclaude-code\u002Fissues\u002F5430). The statusline does try the cheap signals (`process.stdout.columns`, `$COLUMNS`) on the off-chance Anthropic ever fixes the contract, but otherwise falls back to a conservative `120`-column box. **Set `statuslineWidth` in `~\u002F.claude\u002Fcc-statusline-rows.json` to your terminal's actual column count** (run `tput cols` in a normal shell to measure). `statuslineWidthOffset` (default 4) reserves a few columns on the right for Claude Code's own padding.\n\n**Three-layer durability for cum files.** On top of the persistent location, every cum write now goes through a stability pipeline: (1) **monotonic invariant** — read the on-disk file right before write and never let in-memory `cost.total` (or `add` \u002F `rm` \u002F `tok`) drop below the stored value, since these accumulators are monotonic by definition; (2) **single-step backup** — the previous content is atomically copied to `cum-\u003Csid>.bak.json` before each write, so a corrupted file can be hand-recovered; (3) **audit log** — significant cost changes (≥ \\$0.01) append one JSON line each to `~\u002F.claude\u002Fcc-statusline\u002Faudit.log` with timestamp, sid, before \u002F after \u002F delta. The log rotates to `audit.log.1` at ~1 MB.\n\n**Cross-session quota aggregation.** Quotas are global across all your Claude Code sessions, but each session's payload only reflects its own cached observation. The statusline writes a snapshot to `~\u002F.claude\u002Frate-limit-snapshots.json` on every render and aggregates across sessions: it picks the snapshot with the latest live `resets_at` (most recent API observation) and shows MAX `used_percentage` from that group. All sessions converge on the same displayed %.\n\n**All-time cost + tokens by default, rolling-window optional.** The `cost $TOTAL (all time) · $SESSION (this session)` and `tokens TOTAL (SESSION this session)` figures aggregate across `cum-*.json` files in `~\u002F.claude\u002Fcc-statusline\u002F` filtered to the canonical 24-hex session-id shape (so stray test fixtures can't poison the number). Set `aggWindowDays` in `~\u002F.claude\u002Fcc-statusline-rows.json` to `7` \u002F `30` \u002F `90` etc. for a rolling view; the previous 30-day default existed only to mirror tmpdir's eviction interval and is no longer needed now that state lives outside tmpdir.\n\n**Time-based rate-limit rollover.** Claude Code's `rate_limits.*.resets_at` is frozen at the moment of the last API response. If the user leaves the session idle past a reset boundary, the payload says \"87% used\" even though the window has rolled over to 0%. The statusline checks `resets_at` against real time — past expiry, it auto-zeros the bar and computes the countdown against the next rolling 5h\u002F7d boundary.\n\n**Auto session rename for `\u002Fresume` picker.** Claude Code's transcript JSONL supports a `{\"type\":\"custom-title\",\"customTitle\":\"...\"}` entry that drives the `\u002Fresume` picker's display name. `summary-updater.js` injects the current summary (first 40 chars) as a custom-title entry every time it writes, so each session gets a meaningful name instead of a UUID — no more guessing which hash is which.\n\n**Whole-session summary with compression.** The summary is meant to capture the entire session arc, not the latest topic. The summary-updater prompt enforces a 120-char cap with explicit compression rules (merge related sub-topics, drop the least-significant older item) so new topics displace less-significant old ones rather than the most-recent work being truncated.\n\n## Customize which rows you see\n\nDon't want every row? Use the `\u002Fcc-statusline:rows` slash command (shipped with the plugin; saves to `~\u002F.claude\u002Fcc-statusline-rows.json`):\n\n```\n\u002Fcc-statusline:rows                      — show current state\n\u002Fcc-statusline:rows off                  — master switch: hide statusline entirely\n\u002Fcc-statusline:rows on                   — master switch: re-enable\n\u002Fcc-statusline:rows hide agents edited   — turn listed rows off\n\u002Fcc-statusline:rows show agents          — turn listed rows on\n\u002Fcc-statusline:rows only cost quota      — enable listed, disable rest\n\u002Fcc-statusline:rows toggle history       — flip listed rows\n\u002Fcc-statusline:rows reset                — all on\n```\n\n12 row keys: `summary`, `dir`, `repo`, `model`, `duration`, `cost`, `usage`, `quota`, `agents`, `memory_mcp`, `edited`, `history`.\n\nThe layout auto-collapses when cells go empty — hide an entire column and the split layout merges into full-width rows; hide the whole split block and the top border fuses with the next section (no redundant horizontal lines).\n\n### Configuration knobs\n\n`~\u002F.claude\u002Fcc-statusline-rows.json` also accepts these non-row settings (full reference in `commands\u002Frows.md`):\n\n| key | default | what it does |\n|-----|---------|--------------|\n| `summaryInterval` | `10` | UserPromptSubmit events between session-summary nudges |\n| `aggWindowDays` | `0` (all time) | rolling window for cross-session cost \u002F tokens aggregate; e.g. `7` \u002F `30` \u002F `90` |\n| `statuslineWidth` | _(auto, fallback `120`)_ | hard-set box width in columns — measure with `tput cols` from a normal shell |\n| `statuslineWidthOffset` | `4` | columns reserved on the right for Claude Code's own padding |\n\n## Without hooks\n\nThe statusline works without the hooks — you just won't see agents, edited files, message history, compact count, session summary, or active session time. Quotas, cost, model, git, tokens, memory, and MCP all work from the built-in statusline JSON payload + the auto-spawned MCP refresher.\n\n## Known limitations\n\n- Claude Code does not pass terminal width to statusline commands ([issue #5430, closed as not planned](https:\u002F\u002Fgithub.com\u002Fanthropics\u002Fclaude-code\u002Fissues\u002F5430)) and `process.stdout.columns` \u002F `tput cols` \u002F `$COLUMNS` are all unreliable inside the hook. The statusline defaults to a conservative 120-column box; **set `statuslineWidth` in `~\u002F.claude\u002Fcc-statusline-rows.json` to your terminal's actual column count** (run `tput cols` in a normal shell to measure). `statuslineWidthOffset` (default 4) reserves columns on the right for Claude Code's own padding.\n- MCP server state shown by the statusline comes from `claude mcp list` (a fresh probe at refresh time). Claude Code's `\u002Fmcp` UI shows the running session's cached state. The two can disagree if a server's connection has changed since the session started — the statusline reflects the latest probe, the UI reflects the session's view.\n- `claude mcp list` does not expose all built-in bridges (e.g. `claude-in-chrome`), so the statusline's MCP count can be lower than what `\u002Fmcp` shows.\n- Claude Code does not currently expose live MCP state in the statusline JSON payload ([issue #5511](https:\u002F\u002Fgithub.com\u002Fanthropics\u002Fclaude-code\u002Fissues\u002F5511)) — once it does, the auto-spawned refresher won't be needed.\n\n## License\n\nMIT\n","cc-statusline 是一个为 Claude Code 设计的全面状态行仪表盘，能够一目了然地展示会话信息、配额条、代理跟踪器、MCP 健康状况、消息历史等。该项目采用 JavaScript 编写，具备自动更新的会话摘要、目录和代码行更改统计、仓库及分支信息、成本监控、模型性能指示、会话时长计算等功能，并支持通过配置文件自定义显示内容。适用于需要实时掌握开发环境状态的开发者，特别是在使用 Claude Code 进行编程或调试时，能够显著提升工作效率与体验。",2,"2026-06-11 02:43:15","CREATED_QUERY"]