From 5a9192f7f9c2462647942d4bd29a3298c2946d9f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 11 Jul 2026 07:25:39 +0000 Subject: [PATCH] Add Claude Code statusline showing folder, git branch, model, and context bar --- claude-code/README.md | 37 ++++++++++ claude-code/statusline.js | 138 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 claude-code/README.md create mode 100755 claude-code/statusline.js diff --git a/claude-code/README.md b/claude-code/README.md new file mode 100644 index 0000000..2f0120c --- /dev/null +++ b/claude-code/README.md @@ -0,0 +1,37 @@ +# statusline.js + +A [Claude Code](https://claude.com/claude-code) `statusLine` script that shows: + +- 📁 current folder +- 🌿 git branch +- 🤖 active model +- context-window usage as a colored progress bar (green/yellow/red) + +## Setup + +Add to `~/.claude/settings.json` (or your project's `.claude/settings.json`): + +```json +{ + "statusLine": { + "type": "command", + "command": "node /path/to/claude-code/statusline.js" + } +} +``` + +Requires Node.js (already a dependency of Claude Code itself) and `git` on `PATH`. + +## Context window size + +Context usage defaults to a 200K token window. Override with an env var if +your model uses a different window, e.g.: + +```json +{ + "statusLine": { + "type": "command", + "command": "CLAUDE_CONTEXT_WINDOW=1000000 node /path/to/claude-code/statusline.js" + } +} +``` diff --git a/claude-code/statusline.js b/claude-code/statusline.js new file mode 100755 index 0000000..2f051ea --- /dev/null +++ b/claude-code/statusline.js @@ -0,0 +1,138 @@ +#!/usr/bin/env node +// Claude Code statusLine script. +// Shows: folder, git branch, model, and a context-usage progress bar. +// Configure in ~/.claude/settings.json: +// "statusLine": { "type": "command", "command": "node /path/to/statusline.js" } + +const fs = require("fs"); +const path = require("path"); +const { execSync } = require("child_process"); + +const RESET = "\x1b[0m"; +const DIM = "\x1b[2m"; +const CYAN = "\x1b[36m"; +const MAGENTA = "\x1b[35m"; +const BLUE = "\x1b[34m"; +const GREEN = "\x1b[32m"; +const YELLOW = "\x1b[33m"; +const RED = "\x1b[31m"; + +const CONTEXT_WINDOW = + Number(process.env.CLAUDE_CONTEXT_WINDOW) > 0 + ? Number(process.env.CLAUDE_CONTEXT_WINDOW) + : 200000; +const BAR_WIDTH = 10; + +function readStdin() { + try { + return fs.readFileSync(0, "utf8"); + } catch { + return ""; + } +} + +function getFolder(cwd) { + if (!cwd) return "~"; + return path.basename(cwd) || cwd; +} + +function getGitBranch(cwd) { + try { + const branch = execSync("git rev-parse --abbrev-ref HEAD", { + cwd, + stdio: ["ignore", "pipe", "ignore"], + }) + .toString() + .trim(); + return branch === "HEAD" ? "detached" : branch; + } catch { + return null; + } +} + +function getContextUsage(transcriptPath) { + if (!transcriptPath || !fs.existsSync(transcriptPath)) return null; + + let lines; + try { + lines = fs.readFileSync(transcriptPath, "utf8").split("\n"); + } catch { + return null; + } + + for (let i = lines.length - 1; i >= 0; i--) { + const line = lines[i].trim(); + if (!line) continue; + let entry; + try { + entry = JSON.parse(line); + } catch { + continue; + } + const usage = entry?.message?.usage; + if (usage) { + const used = + (usage.input_tokens || 0) + + (usage.cache_creation_input_tokens || 0) + + (usage.cache_read_input_tokens || 0); + if (used > 0) return used; + } + } + return null; +} + +function renderBar(percent) { + const filled = Math.round((clamp(percent, 0, 100) / 100) * BAR_WIDTH); + const empty = BAR_WIDTH - filled; + const color = percent >= 90 ? RED : percent >= 70 ? YELLOW : GREEN; + return `${color}${"█".repeat(filled)}${DIM}${"░".repeat(empty)}${RESET}`; +} + +function clamp(n, min, max) { + return Math.min(max, Math.max(min, n)); +} + +function formatTokens(n) { + if (n >= 1000) return `${(n / 1000).toFixed(1)}K`; + return String(n); +} + +function main() { + const raw = readStdin(); + let data = {}; + try { + data = JSON.parse(raw); + } catch { + data = {}; + } + + const cwd = data?.workspace?.current_dir || data?.cwd || process.cwd(); + const modelName = data?.model?.display_name || data?.model?.id || "unknown"; + const transcriptPath = data?.transcript_path; + + const folder = getFolder(cwd); + const branch = getGitBranch(cwd); + const used = getContextUsage(transcriptPath); + + const parts = [`${CYAN}📁 ${folder}${RESET}`]; + + if (branch) { + parts.push(`${MAGENTA}🌿 ${branch}${RESET}`); + } + + parts.push(`${BLUE}🤖 ${modelName}${RESET}`); + + if (used !== null) { + const percent = clamp((used / CONTEXT_WINDOW) * 100, 0, 100); + const bar = renderBar(percent); + parts.push( + `${bar} ${percent.toFixed(0)}% (${formatTokens(used)}/${formatTokens( + CONTEXT_WINDOW + )})` + ); + } + + process.stdout.write(parts.join(`${DIM} | ${RESET}`) + "\n"); +} + +main();