Appearance
Claude Code Status Line
Customize the Claude Code status bar with a script that displays context window usage, model name, working directory, and repository name with branch.
How it works
Add a statusLine entry to ~/.claude/settings.json that points to a script. Claude Code pipes a JSON object to the script's stdin on each render. The script prints a single line to stdout, which replaces the default status bar.
json
{
"statusLine": {
"type": "command",
"command": "node ~/.claude/scripts/statusline.js"
}
}Available data
The JSON object piped to stdin includes:
| Field | Description |
|---|---|
model.id | Model identifier (e.g. claude-opus-4-6) |
model.display_name | Short model name (e.g. Opus) |
workspace.current_dir | Current working directory |
workspace.project_dir | Original project directory |
transcript_path | Path to the conversation transcript |
context_window.context_window_size | Max tokens (200k or 1M depending on model) |
context_window.used_percentage | Pre-calculated context usage (0–100) |
context_window.remaining_percentage | Pre-calculated context remaining (0–100) |
cost.total_cost_usd | Session cost so far |
version | Claude Code version string |
Some fields appear conditionally: vim (when vim mode is on), worktree (during worktree sessions), rate_limits (for Claude.ai subscribers after the first API response).
Reference implementation
Save to ~/.claude/scripts/statusline.js, then chmod +x that file.
Each segment is rendered by its own function and joined with spacing. Shows a 5-char context usage progress bar (color-coded by fill level), the model name, the working directory, and repoName@branch where repoName is parsed from the origin remote URL (so it reflects the actual repo name, not the CWD or worktree folder).
javascript
#!/usr/bin/env node
const fs = require("node:fs");
const path = require("node:path");
const RESET = "\x1b[0m";
const YELLOW = "\x1b[93m";
const RED = "\x1b[91m";
function renderContext(data) {
const ctx = data.context_window ?? {};
const rate = ctx.used_percentage ?? 0;
const barLength = 5;
const filledLength = Math.round(barLength * rate / 100);
const bar = "█".repeat(filledLength) + "░".repeat(barLength - filledLength);
let barColor = "";
if (rate >= 70) barColor = RED;
else if (rate >= 40) barColor = YELLOW;
return `${barColor}${bar}${RESET} ${rate.toFixed(0)}%`;
}
function renderModel(data) {
return `🧠 ${data.model.display_name}`;
}
function renderWorkingDir(data) {
const currentDir = path.basename(data.workspace.current_dir);
return `📁 ${currentDir}`;
}
function renderRepo(data) {
let gitDir = null;
let dir = data.workspace.current_dir;
while (dir !== path.dirname(dir)) {
const candidate = path.join(dir, ".git");
if (fs.existsSync(candidate)) {
const stat = fs.statSync(candidate);
if (stat.isDirectory()) {
gitDir = candidate;
} else if (stat.isFile()) {
try {
const contents = fs.readFileSync(candidate, "utf8").trim();
const match = contents.match(/^gitdir:\s*(.+)$/);
if (match) gitDir = path.resolve(dir, match[1]);
} catch {}
}
break;
}
dir = path.dirname(dir);
}
if (!gitDir) return "";
let commonDir = gitDir;
const commonDirFile = path.join(gitDir, "commondir");
if (fs.existsSync(commonDirFile)) {
try {
const rel = fs.readFileSync(commonDirFile, "utf8").trim();
commonDir = path.resolve(gitDir, rel);
} catch {}
}
let branch = "";
try {
const ref = fs.readFileSync(path.join(gitDir, "HEAD"), "utf8").trim();
if (ref.startsWith("ref: refs/heads/")) {
branch = ref.replace("ref: refs/heads/", "");
}
} catch {}
if (!branch) return "";
let repoName = null;
try {
const config = fs.readFileSync(path.join(commonDir, "config"), "utf8");
const match = config.match(/\[remote "origin"\][^\[]*?url\s*=\s*([^\n]+)/);
if (match) {
repoName = match[1].trim().replace(/\.git$/, "").split(/[:/]/).pop();
}
} catch {}
if (!repoName) repoName = path.basename(path.dirname(commonDir));
return `🔀 ${repoName}@${branch}`;
}
let input = "";
process.stdin.on("data", chunk => input += chunk);
process.stdin.on("end", () => {
const data = JSON.parse(input);
const parts = [
renderContext(data),
renderModel(data),
renderWorkingDir(data),
renderRepo(data),
].filter(Boolean);
process.stdout.write(parts.join(" ") + "\n");
});Disabling temporarily
To revert to the default status bar without losing the script, remove the statusLine entry from ~/.claude/settings.json. The script stays on disk and can be re-enabled by adding the entry back.
Sync
Install the status line script and settings entry on the current machine. Follow this procedure exactly.
1. Install script
Create ~/.claude/scripts/ if it doesn't exist. Write the reference implementation above to ~/.claude/scripts/statusline.js. Make it executable.
2. Add settings entry
Read ~/.claude/settings.json. If a statusLine entry already exists, compare it to the recommended config above. If missing or different, show the user the current and recommended values. Ask whether to update. Apply if confirmed.
3. Preserve other settings
When writing ~/.claude/settings.json, only modify the statusLine key. Preserve all other keys and use 2-space JSON indentation.