Files
custom-statusline/custom_statusline.js
T

121 lines
3.7 KiB
JavaScript
Raw Normal View History

2026-06-10 01:16:12 +00:00
#!/usr/bin/env node
// Claude Code Statusline
// Shows: <directory> | <git branch> | <model> | [===== ] 60%
// Git branch segment is omitted entirely when not in a git repository.
'use strict';
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
2026-06-10 01:16:12 +00:00
const os = require('os');
const R = '\x1b[0m';
const DIM = '\x1b[90m'; // separators
const CYAN = '\x1b[36m'; // directory
const GREEN = '\x1b[32m'; // git branch
const MAGENTA = '\x1b[35m'; // model
const BAR_OK = '\x1b[32m'; // usage < 70%
const BAR_WARN = '\x1b[33m'; // usage 7089%
const BAR_CRIT = '\x1b[31m'; // usage >= 90%
const SEP = `${DIM} | ${R}`;
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
let data = {};
try { if (input.trim()) data = JSON.parse(input); } catch (_) {}
try {
fs.writeFileSync(path.join(os.tmpdir(), 'statusline_data.json'), JSON.stringify(data, null, 2));
} catch (_) {}
2026-06-10 01:16:12 +00:00
// --- Directory ---
const cwd =
(data.workspace && data.workspace.current_dir) ||
data.cwd ||
process.env.PWD ||
process.cwd();
const home = os.homedir().replace(/\\/g, '/');
const cwdNorm = cwd.replace(/\\/g, '/');
const displayDir = cwdNorm.startsWith(home)
? '~' + cwdNorm.slice(home.length)
: cwdNorm;
// --- Git branch ---
let gitBranch = null;
try {
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
cwd,
stdio: ['ignore', 'pipe', 'ignore'],
timeout: 2000,
env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' },
}).toString().trim();
if (branch && branch !== 'HEAD') {
gitBranch = branch;
} else {
try {
gitBranch = execSync('git rev-parse --short HEAD', {
cwd,
stdio: ['ignore', 'pipe', 'ignore'],
timeout: 2000,
env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' },
}).toString().trim() || 'HEAD';
} catch (_) { gitBranch = 'HEAD'; }
}
} catch (_) { gitBranch = null; }
// --- Model ---
const model =
(data.model && data.model.display_name) ||
(data.model && data.model.id) ||
'Claude';
// --- Bar helper ---
const BAR_WIDTH = 8;
function makeBar(pct, color) {
const filled = Math.round((pct / 100) * BAR_WIDTH);
return `${color}` + '█'.repeat(filled) + `${DIM}` + '░'.repeat(BAR_WIDTH - filled) + `${color} ${pct}%${R}`;
}
2026-06-10 01:16:12 +00:00
// --- Context window usage ---
const pct = Math.round(
(data.context_window && data.context_window.used_percentage) || 0
);
const barColor = pct >= 90 ? BAR_CRIT : pct >= 70 ? BAR_WARN : BAR_OK;
const bar = makeBar(pct, barColor);
2026-06-10 01:16:12 +00:00
// --- 5h rate limit + cost ---
const fiveHour = data.rate_limits && data.rate_limits.five_hour;
const totalCost = data.cost && data.cost.total_cost_usd;
let usageSegment = null;
if (fiveHour || totalCost) {
const parts = [];
if (fiveHour) {
const fivePct = Math.round(fiveHour.used_percentage);
const fiveColor = fivePct >= 90 ? BAR_CRIT : fivePct >= 70 ? BAR_WARN : BAR_OK;
const resetDate = new Date(fiveHour.resets_at * 1000);
const hh = String(resetDate.getHours()).padStart(2, '0');
const mm = String(resetDate.getMinutes()).padStart(2, '0');
parts.push(`${hh}:${mm} ${makeBar(fivePct, fiveColor)}`);
}
if (totalCost) {
parts.push(`${DIM}$${totalCost.toFixed(2)}${R}`);
}
usageSegment = parts.join(' ');
}
2026-06-10 01:16:12 +00:00
// --- Assemble ---
const segments = [`${CYAN}${displayDir}${R}`];
if (gitBranch !== null) segments.push(`${GREEN}${gitBranch}${R}`);
segments.push(`${MAGENTA}${model}${R}`);
segments.push(bar);
if (usageSegment !== null) segments.push(usageSegment);
2026-06-10 01:16:12 +00:00
process.stdout.write(segments.join(SEP) + '\n');
});