@promptctl/cc-candybar
v1.26.0
Published
Statusline renderer for Claude Code — a JSON5-configurable DSL with daemon-cached data sources, byte-clean palette-aware composition, and OSC8 click verbs.
Maintainers
Readme
CCCandybar
Powerline statusline for Claude Code — daemon-backed, config-driven, zero-config-file required.
What it is
CCCandybar is a statusline renderer for Claude Code. It shows session cost, context usage, git status, model info, rate-limit utilization, and more, configured through a JSON5 DSL with hot-reloading.
A background daemon caches git state, usage data, and per-session values across concurrent Claude Code sessions. The renderer connects to the daemon over a Unix socket, so every invocation is fast (~50ms budget) and stateful.
Quick start
pnpm dlx @promptctl/cc-candybar@latest installThat single command (re-run it any time to update to the latest release):
- Stages the runtime at a stable path independent of any package-manager cache —
~/Library/Application Support/CCCandybar/on macOS,$XDG_DATA_HOME/cc-candybar/on Linux — asbin/cc-candybar(the prebuilt native render binary for your platform, or a node entry where none exists) besidedist/index.mjs(the daemon + CLI bundle). - On macOS, builds
~/Applications/CCCandybarURLHandler.appand registers thecc-candybar://URL scheme with Launch Services. - Writes the staged
bin/cc-candybarpath as the statusline command into~/.claude/settings.json(skipped with a notice if you've customized the command;--forceoverwrites).
Restart Claude Code. The statusline appears with the bundled default layout — an identity row (directory, gitaculous, toolbar) over a status row (model, context, cacheTimer, block, weekly). On macOS, Cmd-clicking clickable cells fires cc-candybar:// URL verbs that the daemon dispatches (via the URL handler registered in step 2).
Customization
Drop a .cc-candybar.json5 (or .cc-candybar.json — both extensions are accepted) at any of these locations (highest precedence first):
$CC_CANDYBAR_CONFIG(literal path, supports~expansion)<project>/.cc-candybar.json5(then.jsonat the same location)<cwd>/.cc-candybar.json5(then.json)$XDG_CONFIG_HOME/cc-candybar/config.json5(then.json; defaults to~/.config/cc-candybar/config.json5)
JSON is a strict subset of JSON5, so the same parser handles both — .json5 is the documented format (supports inline comments, trailing commas, unquoted keys), .json is the legacy/compat extension. When both exist at the same location, .json5 wins and the bar shows a persistent warning so you can remove the shadowed duplicate.
The file is a complete replacement for the bundled default — no merge layer. Start by copying src/demo/statusline.json5 from the repo as a minimal example, or src/config/default-dsl-config.ts for the full standard library.
// minimal example — user, directory, branch, model, session, clock
{
globals: { palette: 'textual-dark' },
variables: {
user: { kind: 'env', name: 'USER', default: 'anon' },
cwd: { kind: 'input', path: 'workspace.current_dir', default: '?' },
branch: { kind: 'shell', command: 'git branch --show-current',
cache: { ttl: '5s' }, default: '' },
clock: { kind: 'time', layout: '15:04:05', cache: { ttl: '1s' } },
// per-segment hue rotation (degrees), read by the renderer. A literal pins
// it; make it `{ kind: 'state', key: 'hue-step' }` + a stepper widget to
// adjust it live.
'hue.step': { kind: 'literal', value: 14 },
},
segments: {
user: { template: ' {{ .user }} ', bg: 'primary', fg: 'auto' },
directory: { template: ' {{ basename .cwd }} ', bg: 'surface', fg: 'foreground' },
branch: { template: ' {{ .branch }} ', bg: 'accent', fg: 'auto',
when: '{{ ne .branch "" }}' },
clock: { template: ' {{ .clock }} ', bg: 'primary', fg: 'auto' },
},
layout: ['user', 'directory', 'branch', 'clock'],
}Saving the file triggers a hot-reload of every active session.
Architecture
┌─────────────┐ Unix socket ┌──────────────────┐
│ Claude Code │ ──────────────► │ cc-candybar daemon│
│ (hook) │ render req │ │
│ │ ◄────────────── │ git cache │
└─────────────┘ ANSI output │ usage cache │
│ session state │
│ render cache │
└──────────────────┘- Daemon (
src/daemon/) — long-lived background process. One per user. Caches git state via filesystem watchers, usage data, and per-session key/value state. Runs until it exits on an RSS backstop (default 512 MB) or the host restarts; there is no idle or age timeout. - Client (
src/daemon/client.ts) — each Claude Code hook invocation connects to the daemon, sends a render request, and prints the ANSI response. On failure, spawns a fresh daemon and emits empty output. - Renderer (
src/render/,src/segments/) — segments produce styled output from cached data. Themes cascade from defaults through palette resolution using OKLCH color math. - TUI grid (
src/tui/) — CSS Grid-inspired layout engine with breakpoints, column sizing (auto,1fr, fixed), spanning, and automatic culling of empty segments.
Segments
| Segment | Shows | Symbol |
|---------|-------|--------|
| directory | CWD name (full, fish, basename) | — |
| git | Branch, SHA, working tree, upstream, stash, tags | ⎇ |
| model | Current Claude model | ✱ |
| session | Per-session cost/tokens/breakdown | § |
| today | Daily usage with budget monitoring | ☉ |
| context | Context window usage with auto-compact threshold | ◔ |
| block | 5-hour rate-limit utilization | ◱ |
| weekly | 7-day rolling rate-limit utilization | ◑ |
| metrics | Response time, duration, lines changed | ⧖ |
| version | Claude Code version | ◈ |
| tmux | tmux session name | — |
| sessionId | Session identifier (cmd-click to copy) | ⌗ |
| env | Arbitrary environment variable | ⚙ |
Each segment is a DSL declaration with a template (text + interpolation + style functions), a bg/fg palette spec, and optional when predicate. Templates compose freely — every formatter in the bundled function library (formatCost, formatTokens, formatLongTimeRemaining, budgetStatus, link, urlEncode, the sprig string/list/dict library, …) is available in every segment.
Themes
The DSL config picks a base palette via globals.palette (e.g. textual-dark, gruvbox). Each segment may override with its own palette: field, and bg/fg evaluate as palette spec names (primary, surface, panel, accent, foreground, auto, warning, error, …). Color math runs through OKLCH for perceptual uniformity; the hue.step variable (read via the conventional HUE_STEP_VAR name) rotates adjacent segments by that many degrees to keep them visually distinct without authoring per-segment colors — a literal pins it, a state var driven by a stepper widget makes it adjustable live.
Installation
Requires Node.js 20.19+ (within the 20.x line) or 22.12+, Claude Code, and Git 2.0+. For best display, install a Nerd Font so the powerline glyphs render correctly.
Manual setup
Run the install once to stage the runtime, then point ~/.claude/settings.json at the staged entry (this is also exactly what install writes):
{
"statusLine": {
"type": "command",
"command": "'/Users/you/Library/Application Support/CCCandybar/bin/cc-candybar'"
}
}Developing against a checkout
just deploy builds dist/index.mjs and stages the native binary at bin/cc-candybar-native — point your statusline command at that path. On a machine without cargo, pnpm install && pnpm build builds only the bundle — point your statusline at the committed bin/cc-candybar node entry instead. Either way the bar renders HEAD; the daemon watches the built bundle and respawns itself on rebuild.
Config file
Customization lives in .cc-candybar.json5. See the Customization section above for the resolution order. Saved edits hot-reload — no restart needed.
Contributing
Contributions welcome. See CONTRIBUTORS.md for people who have contributed outside of GitHub PRs.
