Skip to content

Instantly share code, notes, and snippets.

@damieng
Created February 19, 2026 11:01
Show Gist options
  • Select an option

  • Save damieng/2b8d1e41e8094d285b64504be0fc97a5 to your computer and use it in GitHub Desktop.

Select an option

Save damieng/2b8d1e41e8094d285b64504be0fc97a5 to your computer and use it in GitHub Desktop.
Node port of the bash status line for claude at https://github.com/ykdojo/claude-code-tips/blob/main/scripts/context-bar.sh
#!/usr/bin/env node
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
let input = '';
process.stdin.on('data', chunk => input += chunk);
process.stdin.on('end', () => {
const data = JSON.parse(input);
// Colors (256-color ANSI)
const THEMES = {
orange: '38;5;173', blue: '38;5;74', teal: '38;5;66',
green: '38;5;71', lavender: '38;5;139', rose: '38;5;132',
gold: '38;5;136', slate: '38;5;60', cyan: '38;5;37'
};
const COLOR = 'blue';
const C_ACCENT = `\x1b[${THEMES[COLOR]}m`;
const C_GRAY = '\x1b[38;5;245m';
const C_BAR_EMPTY = '\x1b[38;5;238m';
const C_RESET = '\x1b[0m';
// Model & directory
const model = data.model?.display_name || data.model?.id || '?';
const dir = path.basename(data.cwd || data.workspace?.current_dir || '');
const maxContext = data.context_window?.context_window_size || 200000;
const maxK = Math.floor(maxContext / 1000);
const transcriptPath = data.transcript_path || '';
// Git info
let gitInfo = '';
try {
execSync('git rev-parse --git-dir', { stdio: 'ignore' });
const branch = execSync('git branch --show-current', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim() || 'HEAD';
// Uncommitted changes
const porcelain = execSync('git status --porcelain', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
let changes = '';
if (porcelain) {
const lines = porcelain.split('\n').filter(Boolean);
if (lines.length === 1) {
const fname = lines[0].substring(3).trim().replace(/^"(.*)"$/, '$1');
changes = ` ${path.basename(fname)}`;
} else {
changes = ` ${lines.length} files`;
}
}
// Sync status
let sync = '';
try {
const lr = execSync('git rev-list --left-right --count HEAD...@{upstream}', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
const [ahead, behind] = lr.split(/\s+/).map(Number);
if (ahead === 0 && behind === 0) {
// Fetch age
let fetchAgo = '';
try {
const gitDir = execSync('git rev-parse --git-dir', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
const fetchHead = path.join(gitDir, 'FETCH_HEAD');
if (fs.existsSync(fetchHead)) {
const ageSec = Math.floor((Date.now() - fs.statSync(fetchHead).mtimeMs) / 1000);
if (ageSec < 60) fetchAgo = ' <1m ago';
else if (ageSec < 3600) fetchAgo = ` ${Math.floor(ageSec / 60)}m ago`;
else if (ageSec < 86400) fetchAgo = ` ${Math.floor(ageSec / 3600)}h ago`;
else fetchAgo = ` ${Math.floor(ageSec / 86400)}d ago`;
}
} catch {}
sync = ` synced${fetchAgo}`;
} else {
const parts = [];
if (ahead > 0) parts.push(`${ahead} ahead`);
if (behind > 0) parts.push(`${behind} behind`);
sync = ` ${parts.join(', ')}`;
}
} catch {
sync = ' no upstream';
}
gitInfo = ` | \u{1F500}${branch}${changes}${sync}`;
} catch {}
// Context tokens from transcript
let contextLength = 20000; // baseline estimate
let approximate = true;
if (transcriptPath && fs.existsSync(transcriptPath)) {
try {
const raw = fs.readFileSync(transcriptPath, 'utf8');
const lines = raw.trim().split('\n').filter(Boolean);
// Walk backwards to find last message with usage (not sidechain, not error)
for (let i = lines.length - 1; i >= 0; i--) {
try {
const entry = JSON.parse(lines[i]);
if (entry.message?.usage && !entry.isSidechain && !entry.isApiErrorMessage) {
const u = entry.message.usage;
const tokens = (u.input_tokens || 0) + (u.cache_read_input_tokens || 0) + (u.cache_creation_input_tokens || 0);
if (tokens > 0) {
contextLength = tokens;
approximate = false;
}
break;
}
} catch {}
}
} catch {}
}
const pct = Math.floor((contextLength * 100) / maxContext);
const pctStr = approximate ? `~${pct}%` : `${pct}%`;
// Progress bar (10 cells, per-cell thresholds)
let bar = '';
for (let i = 0; i < 10; i++) {
const cellPct = pct - (i * 10);
if (cellPct >= 8) bar += `${C_ACCENT}\u{2588}${C_RESET}`;
else if (cellPct >= 3) bar += `${C_ACCENT}\u{2584}${C_RESET}`;
else bar += `${C_BAR_EMPTY}\u{2591}${C_RESET}`;
}
// Line 1: model | dir | git | bar
const line1 = `${C_ACCENT}${model}${C_GRAY} | \u{1F4C1}${dir}${gitInfo} | ${bar} ${pctStr} of ${maxK}k tokens${C_RESET}`;
// Line 2: last user message from transcript
let line2 = '';
if (transcriptPath && fs.existsSync(transcriptPath)) {
try {
const raw = fs.readFileSync(transcriptPath, 'utf8');
const lines = raw.trim().split('\n').filter(Boolean);
for (let i = lines.length - 1; i >= 0; i--) {
try {
const entry = JSON.parse(lines[i]);
if (entry.type === 'user') {
let text = '';
const content = entry.message?.content;
if (typeof content === 'string') {
text = content;
} else if (Array.isArray(content)) {
text = content
.filter(c => c.type === 'text')
.map(c => c.text)
.join(' ');
}
text = text.replace(/\s+/g, ' ').trim();
if (!text || text.startsWith('[Request interrupted') || text.startsWith('[Request cancelled')) {
continue;
}
// Truncate to reasonable width
const maxLen = (process.stdout.columns || 80) - 4;
if (text.length > maxLen) {
text = text.substring(0, maxLen - 3) + '...';
}
line2 = `${C_GRAY}\u{1F4AC} ${text}${C_RESET}`;
break;
}
} catch {}
}
} catch {}
}
console.log(line1);
if (line2) console.log(line2);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment