-
-
Save gapurov/75eb3bfe6393caab090a71eb9c4601c5 to your computer and use it in GitHub Desktop.
Ny Claude Code Status Bar - see https://x.com/steipete/status/1956465968835915897
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"$schema": "https://json.schemastore.org/claude-code-settings.json", | |
"includeCoAuthoredBy": false, | |
"permissions": { | |
"defaultMode": "bypassPermissions" | |
}, | |
"model": "opusplan", | |
"statusLine": { | |
"type": "command", | |
"command": "/Users/steipete/.claude/statusline-worktree.js" | |
}, | |
"feedbackSurveyState": { | |
"lastShownTime": 1754086675675 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bun | |
"use strict"; | |
const fs = require("fs"); | |
const { execSync } = require("child_process"); | |
const path = require("path"); | |
// ANSI color constants | |
const c = { | |
cy: '\033[36m', // cyan | |
g: '\033[32m', // green | |
m: '\033[35m', // magenta | |
gr: '\033[90m', // gray | |
r: '\033[31m', // red | |
o: '\033[38;5;208m', // orange | |
y: '\033[33m', // yellow | |
x: '\033[0m' // reset | |
}; | |
// Unified execution function with error handling | |
const exec = (cmd, cwd = null) => { | |
try { | |
const options = { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }; | |
if (cwd) options.cwd = cwd; | |
return execSync(cmd, options).trim(); | |
} catch { | |
return ''; | |
} | |
}; | |
// Fast context percentage calculation | |
function getContextPct(transcriptPath) { | |
if (!transcriptPath) return "0"; | |
try { | |
const data = fs.readFileSync(transcriptPath, "utf8"); | |
const lines = data.split('\n'); | |
// Scan last 50 lines only for performance | |
let latestUsage = null; | |
let latestTs = -Infinity; | |
for (let i = Math.max(0, lines.length - 50); i < lines.length; i++) { | |
const line = lines[i].trim(); | |
if (!line) continue; | |
try { | |
const j = JSON.parse(line); | |
const ts = typeof j.timestamp === "string" ? new Date(j.timestamp).getTime() : j.timestamp; | |
const usage = j.message?.usage; | |
if (ts > latestTs && usage && j.message?.role === "assistant") { | |
latestTs = ts; | |
latestUsage = usage; | |
} | |
} catch {} | |
} | |
if (latestUsage) { | |
const used = (latestUsage.input_tokens || 0) + (latestUsage.output_tokens || 0) + | |
(latestUsage.cache_read_input_tokens || 0) + (latestUsage.cache_creation_input_tokens || 0); | |
const pct = Math.min(100, (used * 100) / 160000); | |
return pct >= 90 ? pct.toFixed(1) : Math.round(pct).toString(); | |
} | |
} catch {} | |
return "0"; | |
} | |
// Cached PR lookup with optimized file operations | |
function getPR(branch, workingDir) { | |
const gitDir = exec('git rev-parse --git-common-dir', workingDir); | |
if (!gitDir) return ''; | |
const cacheFile = `${gitDir}/statusbar/pr-${branch}`; | |
const tsFile = `${cacheFile}.timestamp`; | |
// Check cache freshness (60s TTL) | |
try { | |
const age = Math.floor(Date.now() / 1000) - parseInt(fs.readFileSync(tsFile, 'utf8')); | |
if (age < 60) return fs.readFileSync(cacheFile, 'utf8').trim(); | |
} catch {} | |
// Fetch and cache new PR data | |
const url = exec(`gh pr list --head "${branch}" --json url --jq '.[0].url // ""'`, workingDir); | |
try { | |
fs.mkdirSync(path.dirname(cacheFile), { recursive: true }); | |
fs.writeFileSync(cacheFile, url); | |
fs.writeFileSync(tsFile, Math.floor(Date.now() / 1000).toString()); | |
} catch {} | |
return url; | |
} | |
// Main statusline function | |
function statusline() { | |
let input; | |
try { | |
input = JSON.parse(fs.readFileSync(0, "utf8")); | |
} catch { | |
input = {}; | |
} | |
const currentDir = input.workspace?.current_dir; | |
const model = input.model?.display_name; | |
// Build model display with context | |
let modelDisplay = ''; | |
if (model) { | |
const abbrev = model.includes('Opus') ? 'O' : model.includes('Sonnet') ? 'S' : model.includes('Haiku') ? 'H' : '?'; | |
const pct = getContextPct(input.transcript_path); | |
const pctNum = parseFloat(pct); | |
const pctColor = pctNum >= 90 ? c.r : pctNum >= 70 ? c.o : pctNum >= 50 ? c.y : c.gr; | |
modelDisplay = ` ${c.gr}(${pctColor}${pct}% ${c.gr}${abbrev})${c.x}`; | |
} | |
// Handle non-directory cases | |
if (!currentDir) return `${c.cy}~${c.x}${modelDisplay}`; | |
// Don't chdir - work with the provided directory directly | |
const workingDir = currentDir; | |
// Check git repo status | |
if (exec('git rev-parse --is-inside-work-tree', workingDir) !== 'true') { | |
return `${c.cy}${workingDir.replace(process.env.HOME, '~')}${c.x}${modelDisplay}`; | |
} | |
// Get git info in one batch | |
const branch = exec('git branch --show-current', workingDir); | |
const gitDir = exec('git rev-parse --git-dir', workingDir); | |
const repoUrl = exec('git remote get-url origin', workingDir); | |
const repoName = repoUrl ? path.basename(repoUrl, '.git') : ''; | |
// Smart path display logic | |
const prUrl = getPR(branch, workingDir); | |
const homeProjects = `${process.env.HOME}/Projects/${repoName}`; | |
let displayDir = ''; | |
if (workingDir === homeProjects) { | |
displayDir = prUrl ? '' : `${workingDir.replace(process.env.HOME, '~')} `; | |
} else if (workingDir.startsWith(homeProjects + '/')) { | |
displayDir = `${workingDir.slice(homeProjects.length + 1)} `; | |
} else { | |
displayDir = `${workingDir.replace(process.env.HOME, '~')} `; | |
} | |
// Git status processing (optimized) | |
const statusOutput = exec('git status --porcelain', workingDir); | |
let gitStatus = ''; | |
if (statusOutput) { | |
const lines = statusOutput.split('\n'); | |
let added = 0, modified = 0, deleted = 0, untracked = 0; | |
for (const line of lines) { | |
if (!line) continue; | |
const s = line.slice(0, 2); | |
if (s[0] === 'A' || s === 'M ') added++; | |
else if (s[1] === 'M' || s === ' M') modified++; | |
else if (s[0] === 'D' || s === ' D') deleted++; | |
else if (s === '??') untracked++; | |
} | |
if (added) gitStatus += ` +${added}`; | |
if (modified) gitStatus += ` ~${modified}`; | |
if (deleted) gitStatus += ` -${deleted}`; | |
if (untracked) gitStatus += ` ?${untracked}`; | |
} | |
// Line changes calculation | |
const diffOutput = exec('git diff --numstat', workingDir); | |
if (diffOutput) { | |
let totalAdd = 0, totalDel = 0; | |
for (const line of diffOutput.split('\n')) { | |
if (!line) continue; | |
const [add, del] = line.split('\t'); | |
totalAdd += parseInt(add) || 0; | |
totalDel += parseInt(del) || 0; | |
} | |
const delta = totalAdd - totalDel; | |
if (delta) gitStatus += delta > 0 ? ` Δ+${delta}` : ` Δ${delta}`; | |
} | |
// Format final output | |
const prDisplay = prUrl ? ` ${c.gr}${prUrl}${c.x}` : ''; | |
const isWorktree = gitDir.includes('/.git/worktrees/'); | |
if (isWorktree) { | |
const worktreeName = path.basename(displayDir.replace(/ $/, '')); | |
const branchDisplay = branch === worktreeName ? '↟' : `${branch}↟`; | |
return `${c.cy}${displayDir}${c.x}${c.m}[${branchDisplay}${gitStatus}]${c.x}${prDisplay}${modelDisplay}`; | |
} else { | |
if (!displayDir) { | |
return `${c.g}[${branch}${gitStatus}]${c.x}${prDisplay}${modelDisplay}`; | |
} else { | |
return `${c.cy}${displayDir}${c.x}${c.g}[${branch}${gitStatus}]${c.x}${prDisplay}${modelDisplay}`; | |
} | |
} | |
} | |
// Output result | |
process.stdout.write(statusline()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment