UNPKG

@owloops/claude-powerline

Version:

Beautiful vim-style powerline statusline for Claude Code with real-time usage tracking, git integration, and custom themes

169 lines (142 loc) 5.29 kB
interface TokenBreakdown { input: number; output: number; cacheCreation: number; cacheRead: number; } export function formatCost(cost: number | null): string { if (cost === null) return "$0.00"; if (cost < 0.01) return "<$0.01"; return `$${cost.toFixed(2)}`; } export function formatTokens(tokens: number | null): string { if (tokens === null) return "0 tokens"; if (tokens === 0) return "0 tokens"; if (tokens >= 1_000_000) { return `${(tokens / 1_000_000).toFixed(1)}M tokens`; } else if (tokens >= 1_000) { return `${(tokens / 1_000).toFixed(1)}K tokens`; } return `${tokens} tokens`; } export function formatTokenBreakdown(breakdown: TokenBreakdown | null): string { if (!breakdown) return "0 tokens"; const parts: string[] = []; if (breakdown.input > 0) { parts.push(`${formatTokenCount(breakdown.input)} in`); } if (breakdown.output > 0) { parts.push(`${formatTokenCount(breakdown.output)} out`); } if (breakdown.cacheCreation > 0 || breakdown.cacheRead > 0) { const totalCached = breakdown.cacheCreation + breakdown.cacheRead; parts.push(`${formatTokenCount(totalCached)} cached`); } return parts.length > 0 ? parts.join(" + ") : "0 tokens"; } export function formatTimeSince(seconds: number): string { if (seconds < 60) return `${seconds}s`; if (seconds < 3600) return `${Math.floor(seconds / 60)}m`; if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`; if (seconds < 604800) return `${Math.floor(seconds / 86400)}d`; return `${Math.floor(seconds / 604800)}w`; } export function formatDuration(seconds: number): string { if (seconds < 60) { return `${seconds.toFixed(0)}s`; } else if (seconds < 3600) { return `${(seconds / 60).toFixed(0)}m`; } else if (seconds < 86400) { return `${(seconds / 3600).toFixed(1)}h`; } else { return `${(seconds / 86400).toFixed(1)}d`; } } const CLAUDE_MODEL_PATTERN = /^(?:(?:global|apac|au|eu|us|us-east-\d|us-west-\d|eu-west-\d|eu-central-\d)\.)?(?:anthropic\.|azure_ai\/|bedrock\/|vertex_ai\/)?claude-(?:(?<family>opus|sonnet|haiku)-(?<newMajor>\d+)(?:-(?<newMinor>\d))?|(?<oldMajor>\d+)(?:-(?<oldMinor>\d))?-(?<oldFamily>opus|sonnet|haiku))(?:[-@]\d{8})?(?:-v\d+:\d+)?(?:-latest)?$/i; export function formatModelName(rawName: string): string { if (!rawName) { return "Claude"; } const match = rawName.trim().match(CLAUDE_MODEL_PATTERN); if (!match?.groups) { return rawName; } const { family, newMajor, newMinor, oldMajor, oldMinor, oldFamily } = match.groups; const modelFamily = family || oldFamily; const major = newMajor || oldMajor; const minor = newMinor || oldMinor; if (modelFamily && major) { const capitalizedFamily = modelFamily.charAt(0).toUpperCase() + modelFamily.slice(1).toLowerCase(); const version = minor ? `${major}.${minor}` : major; return `${capitalizedFamily} ${version}`; } return rawName; } export function abbreviateFishStyle(dirPath: string): string { const sep = dirPath.includes("/") ? "/" : "\\"; const parts = dirPath.split(sep); return parts .map((part, index) => { if (index === parts.length - 1) { return part; } if (part === "~" || part === "") { return part; } return part.charAt(0); }) .join(sep); } export function formatResponseTime(seconds: number): string { if (seconds < 60) { return `${seconds.toFixed(1)}s`; } return `${(seconds / 60).toFixed(1)}m`; } export function formatTokenCount(tokens: number | null): string { return formatTokens(tokens).replace(" tokens", ""); } export function formatBurnRate(rate: number | null | undefined): string { if (rate === null || rate === undefined || rate <= 0) return ""; return rate < 1 ? `${(rate * 100).toFixed(0)}c/h` : `$${rate.toFixed(2)}/h`; } export function collapseHome(dirPath: string, homeDir?: string): string { const home = homeDir ?? globalThis.process?.env?.HOME ?? globalThis.process?.env?.USERPROFILE; if (home && dirPath.startsWith(home)) { return dirPath.replace(home, "~"); } return dirPath; } export function formatTimeRemaining(totalMinutes: number): string { const hours = Math.floor(totalMinutes / 60); const minutes = totalMinutes % 60; return hours > 0 ? `${hours}h ${minutes}m left` : `${minutes}m left`; } export function formatLongTimeRemaining(totalMinutes: number): string { if (totalMinutes >= 1440) { const days = Math.floor(totalMinutes / 1440); const hours = Math.floor((totalMinutes % 1440) / 60); return hours > 0 ? `${days}d ${hours}h` : `${days}d`; } else if (totalMinutes >= 60) { const hours = Math.floor(totalMinutes / 60); const minutes = totalMinutes % 60; return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`; } return `${totalMinutes}m`; } export function minutesUntilReset(epochSeconds: number): number { return Math.round(Math.max(0, epochSeconds * 1000 - Date.now()) / 60000); } export function formatCacheTimerElapsed(seconds: number): string { if (seconds >= 3600) return "1h+"; if (seconds >= 300) return `${Math.floor(seconds / 60)}m`; const m = Math.floor(seconds / 60); const s = Math.floor(seconds % 60); return `${m}:${s.toString().padStart(2, "0")}`; }