statuslin.es

Color-Coded Context

Node status line with an autocompact-aware context bar (subtracts the compaction buffer), project, branch, last-activity time, and 5h/7d rate-limit segments.

MIT licensed · original source

Updated 2026-06-29

25 copies

Preview

Clean repo
◆ Opus 4.8 │ ▪ app │ ⎇ main │ ◷ 16:20 │ 26% (44K/167K) │ 26% ↻2h6m7% ⟳2d0h
New session
◆ Opus 4.8 │ ▪ app │ ⎇ main │ ◷ 16:20 │ 0% (0K/167K)
Dirty branch
◆ Sonnet 4.6 │ ▪ app │ ⎇ feat/auth │ ◷ 16:20 │ 57% (96K/167K) │ 40% ↻1h11m18% ⟳2d21h
Near-full
◆ Opus 4.8 │ ▪ app │ ⎇ main │ ◷ 16:20 │ 100% (182K/167K) │ 88% ↻17m61% ⟳19h59m
1M context
◆ Fable 5 │ ▪ app │ ⎇ main │ ◷ 16:20 │ 66% (640K/967K) │ 33% ↻3h29m80% ⟳4d23h
Post-compact
◆ Haiku 4.5 │ ▪ app │ ⎇ main │ ◷ 16:20 │ 0% (0K/167K) │ 52% ↻4h1m
Worktree
◆ Opus 4.8 │ ▪ feature │ ⎇ worktree-feature │ ◷ 16:20 │ 44% (74K/167K) │ 44% ↻2h49m20% ⟳1d6h
Non-git
◆ Opus 4.8 │ ▪ scratch │ ◷ 16:20 │ 26% (44K/167K)

Source

#!/usr/bin/env node

const path = require('path');
const fs = require('fs');
const os = require('os');
const { execSync } = require('child_process');

const STATE_FILE = path.join(os.tmpdir(), 'claude-status-line-state.json');
const AUTOCOMPACT_BUFFER_TOKENS = 33_000;
const WARN_TOKENS = 60_000;
const COMPACT_TOKENS = 80_000;

const RST = '\x1b[0m';
const DIM = '\x1b[38;5;238m';
const MUTED = '\x1b[38;5;245m';

function formatDuration(epoch) {
  if (!epoch) return '';
  const diff = Math.max(0, Math.round(epoch - Date.now() / 1000));
  if (diff <= 0) return 'now';
  if (diff < 60) return '<1m';
  const d = Math.floor(diff / 86400);
  const h = Math.floor((diff % 86400) / 3600);
  const m = Math.floor((diff % 3600) / 60);
  if (d > 0) return `${d}d${h}h`;
  return h > 0 ? `${h}h${m}m` : `${m}m`;
}

function rateLimitColor(pct) {
  return pct > 80 ? '\x1b[31m' : pct > 50 ? '\x1b[33m' : '\x1b[32m';
}

function formatRateLimit(rl, icon) {
  if (rl?.used_percentage == null) return '';
  const pct = Math.round(rl.used_percentage);
  const reset = formatDuration(rl.resets_at);
  let part = ` │ ${rateLimitColor(pct)}${pct}%${RST}`;
  if (reset) part += ` ${MUTED}${icon}${reset}${RST}`;
  return part;
}

let input = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => { input += chunk; });
process.stdin.on('end', () => {
  try {
    const data = JSON.parse(input);
    const ctx = data.context_window || {};
    const model = data.model || {};
    const cwd = data.cwd || '';

    const windowSize = ctx.context_window_size ?? 200000;
    const effectiveWindow = windowSize - AUTOCOMPACT_BUFFER_TOKENS;
    const rawUsedPct = ctx.used_percentage ?? 0;
    const tokensUsed = Math.round((rawUsedPct / 100) * windowSize);
    const usedPct = Math.min(100, Math.round((tokensUsed / effectiveWindow) * 100));
    const level = tokensUsed >= COMPACT_TOKENS ? 'danger' : tokensUsed >= WARN_TOKENS ? 'warn' : 'ok';
    const color = level === 'danger' ? '\x1b[38;5;208m' : level === 'warn' ? '\x1b[33m' : '\x1b[32m';

    const project = cwd ? path.basename(cwd) : '';
    let branch = '';
    if (cwd) {
      try {
        branch = execSync('git --no-optional-locks branch --show-current',
          { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim().slice(0, 30);
      } catch { /* not a git repo */ }
    }

    const bar = Array.from({ length: 10 }, (_, i) => {
      const filled = usedPct - i * 10;
      const char = filled >= 8 ? '█' : filled >= 3 ? '▄' : null;
      return char ? `${color}${char}${RST}` : `${DIM}░${RST}`;
    }).join('');

    let lastCallMs;
    try {
      const prev = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
      lastCallMs = prev.tokensUsed === tokensUsed ? prev.timestamp : Date.now();
    } catch { lastCallMs = Date.now(); }
    fs.writeFileSync(STATE_FILE, JSON.stringify({ tokensUsed, timestamp: lastCallMs }));
    const last = new Date(lastCallMs);
    const time = `${String(last.getHours()).padStart(2, '0')}:${String(last.getMinutes()).padStart(2, '0')}`;
    const tokensK = (tokensUsed / 1000).toFixed(0);
    const windowK = (effectiveWindow / 1000).toFixed(0);

    let output = `◆ ${(model.display_name ?? 'Claude').replace(/\(1M context\)/, '1M').trim()}`;
    if (project) output += ` │ ▪ ${project}`;
    if (branch) output += ` │ ⎇ ${branch}`;
    output += ` │ ◷ ${time}`;
    output += ` │ ${bar} ${usedPct}% (${tokensK}K/${windowK}K)`;

    const rateLimits = data.rate_limits || {};
    output += formatRateLimit(rateLimits.five_hour, '↻');
    output += formatRateLimit(rateLimits.seven_day, '⟳');

    process.stdout.write(output);
  } catch {
    process.stdout.write('Ctx: --');
  }
});

What it shows

  • Model display name, with '(1M context)' shortened to '1M'
  • Project name, taken from the last path segment of the working directory
  • Current git branch, truncated to 30 characters
  • Time of last activity (HH:MM), tracked by noticing when the token count last changed
  • A 10-segment context usage bar with green, yellow, and orange color levels
  • Context usage as a percentage of the effective window (the full window minus a 33K autocompact buffer)
  • Tokens used and effective window size in thousands, e.g. 44K/167K
  • 5-hour rate limit usage percentage with a countdown to reset (↻)
  • 7-day rate limit usage percentage with a countdown to reset (⟳)

Requirements

  • Node.js runtime
  • The git command on PATH for the branch segment (omitted gracefully if git fails)
  • A terminal with 256-color ANSI support
  • A font that renders Unicode symbols such as ◆ ▪ ⎇ ◷ ↻ ⟳ and block characters; no Nerd Font glyphs are used
  • A writable OS temp directory for its small state file (claude-status-line-state.json)
  • No network access

Behavior notes

  • The branch segment disappears when the working directory is not a git repository, as in the scratch-directory scenario
  • Rate-limit segments only appear for the windows present in the input: both in most scenarios, only the 5-hour segment in the Haiku scenario, and none in the brand-new-session and sub-agent scenarios
  • The displayed percentage is measured against the window minus the 33K autocompact buffer, so it runs higher than Claude Code's raw used_percentage (raw 22% renders as 26%)
  • The percentage is capped at 100 even when tokens exceed the effective window, as in the near-full scenario showing 100% (182K/167K)
  • Bar color is keyed to absolute token count, not percentage: green below 60K tokens, yellow from 60K, orange from 80K, which is why the 1M-context scenario at 640K tokens shows an orange bar at only 66%
  • Rate-limit percentages are colored green up to 50%, yellow above 50%, and red above 80% (88% renders red, 52% and 61% render yellow)
  • A null used_percentage is treated as zero, rendering an empty bar and 0% (0K/167K) in the new-session and post-compact scenarios
  • With a 1M-token window the totals scale accordingly, showing 640K/967K
  • Reset countdowns adapt their units to the remaining time: minutes (↻17m), hours and minutes (↻2h6m), or days and hours (⟳2d0h)
  • In the worktree scenario the project segment shows the worktree directory name and the branch segment shows the worktree's own branch
  • If the stdin JSON cannot be parsed, the script prints the fallback text 'Ctx: --'

More status lines

Dreambase Panel

150 copies

╭─ CONTEXT ────────────────────────────────────────────╮ ████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD ├─ STATS ──────────────────────────────────────────────┤ 44.0K 1.4K $0.41 ⏱ 10m 12s +128/-34 ├─ REPO ───────────────────────────────────────────────┤ Opus 4.8 · main · 16:21 ╰──────────────────────────────────────────────────────╯
app · main · Opus-4.8 · effort high · ctx 22% · 5h 26% ↻2h6m · 7d 7% ↻2d0h
Opus 4.8 [high] ~/app main 16:20:55 | ⛁ ██░░░░░░░░ 22% | 5h ●◔○○○ 26% ↻2h7m | 7d ◔○○○○○○ 7% ↻2d1h │ $0.41 ⏱ 10m
Opus 4.8 main app 44k/200k $0.41

Activity Feed

36 copies

Opus 4.8 high ctx 22% app (main) PR #1287 $0.41 · 10m +128 -34 Session 74% left Resets in 2h 6m Weekly 93% left Resets in 2d 0h Fable 85% left Resets in 2d 0h ◐ Read:index.ts ✓ Read×1 ✓ Bash×1 ✓ Edit×1 ✓ Grep×1 ◐ code-reviewer Review the change for regressions
████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD 44.0K 1.4K $0.41 ⏱ 10m 12s +128/-34 Opus 4.8 · main · 16:22