Last active
March 18, 2026 21:49
-
-
Save mmatczuk/c1a8551376fff5c937396d2e5aafdb45 to your computer and use it in GitHub Desktop.
Claude Code status line: Repo(branch) | session/today cost | duration | model
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 | |
| import { readFileSync, writeFileSync, existsSync } from "fs"; | |
| import { execSync } from "child_process"; | |
| import { homedir } from "os"; | |
| import { join } from "path"; | |
| const input = JSON.parse(readFileSync("/dev/stdin", "utf8")); | |
| const modelRaw: string = input.model?.display_name ?? input.model ?? ""; | |
| const sessionId: string = input.session_id ?? ""; | |
| const sessionCost: number = input.cost?.total_cost_usd ?? 0; | |
| const totalDurationMs: number = input.cost?.total_duration_ms ?? 0; | |
| const ctxPct: number | null = input.context_window?.used_percentage ?? null; | |
| const cwd: string = input.cwd ?? process.cwd(); | |
| // --- Model short name: "Claude Sonnet 4.6" → "Sonnet 4.6" --- | |
| function shortModel(name: string): string { | |
| const m = name.match(/\b(Opus|Sonnet|Haiku)\b.*?(\d+\.\d+)/i); | |
| if (!m) return name; | |
| return `${m[1]} ${m[2]}`; | |
| } | |
| // --- Repo label: just Repo(branch), no user prefix --- | |
| function repoLabel(dir: string): string { | |
| try { | |
| const remote = execSync("git remote get-url origin", { cwd: dir, stdio: ["pipe","pipe","pipe"] }) | |
| .toString().trim(); | |
| const match = remote.match(/[:/]([^/:]+\/([^/]+?))(?:\.git)?$/); | |
| const repo = match ? match[2] : dir; | |
| const branch = execSync("git rev-parse --abbrev-ref HEAD", { cwd: dir, stdio: ["pipe","pipe","pipe"] }) | |
| .toString().trim(); | |
| return `${repo}(${branch})`; | |
| } catch { /* no remote */ } | |
| try { | |
| execSync("git rev-parse --git-dir", { cwd: dir, stdio: ["pipe","pipe","pipe"] }); | |
| const branch = execSync("git rev-parse --abbrev-ref HEAD", { cwd: dir, stdio: ["pipe","pipe","pipe"] }) | |
| .toString().trim(); | |
| return `${dir}(${branch})`; | |
| } catch { | |
| return dir; | |
| } | |
| } | |
| // --- Session duration --- | |
| function fmtDuration(ms: number): string { | |
| const totalMin = Math.floor(ms / 60_000); | |
| const h = Math.floor(totalMin / 60); | |
| const m = totalMin % 60; | |
| return h > 0 ? `${h}h ${m}m` : `${m}m`; | |
| } | |
| // --- Ledger: cost per session (keyed, one entry per session_id) --- | |
| interface LedgerEntry { ts: number; session_id: string; cost: number; } | |
| const LEDGER = join(homedir(), ".claude", "cost-ledger.jsonl"); | |
| function readLines<T>(path: string): T[] { | |
| if (!existsSync(path)) return []; | |
| return readFileSync(path, "utf8").split("\n") | |
| .filter(Boolean).flatMap(line => { try { return [JSON.parse(line) as T]; } catch { return []; } }); | |
| } | |
| const now = Date.now() / 1000; | |
| let entries = readLines<LedgerEntry>(LEDGER); | |
| if (sessionId) { | |
| entries = entries.filter(e => e.session_id !== sessionId); | |
| entries.push({ ts: now, session_id: sessionId, cost: sessionCost }); | |
| writeFileSync(LEDGER, entries.map(e => JSON.stringify(e)).join("\n") + "\n", "utf8"); | |
| } | |
| const midnight = (() => { const d = new Date(); d.setHours(0,0,0,0); return d.getTime()/1000; })(); | |
| const todayCost = entries.filter(e => e.ts >= midnight).reduce((s, e) => s + e.cost, 0); | |
| // --- Output --- | |
| const fmt = (n: number) => `$${n.toFixed(2)}`; | |
| const repo = repoLabel(cwd); | |
| const duration = fmtDuration(totalDurationMs); | |
| const model = shortModel(modelRaw); | |
| const ctx = ctxPct !== null ? ` (${Math.round(ctxPct)}%)` : ""; | |
| process.stdout.write(`${repo} | 💰 ${fmt(sessionCost)} (${duration}) session / ${fmt(todayCost)} today | 🤖 ${model}${ctx}\n`); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment