claude-code-usage-statusline
Version:
Clean statusline for Claude Code showing session info, git status, context usage, and API rate limits
181 lines (162 loc) • 5.32 kB
text/typescript
import type { StatuslineConfig } from "../statusline.config";
import { loadConfig } from "./lib/config";
import { getContextData } from "./lib/context";
import {
colors,
formatBranch,
formatPath,
formatProgressBar,
formatResetTime,
formatSession,
symbols,
} from "./lib/formatters";
import { getGitStatus } from "./lib/git";
import type { HookInput } from "./lib/types";
import { getUsageLimits } from "./lib/usage-limits";
function buildFirstLine(
branch: string,
dirPath: string,
modelName: string,
showSonnetModel: boolean,
separator: string,
): string {
const isSonnet = modelName.toLowerCase().includes("sonnet");
const sep = `${colors.GRAY}${separator}${colors.LIGHT_GRAY}`;
if (isSonnet && !showSonnetModel) {
return `${colors.LIGHT_GRAY}${branch} ${sep} ${dirPath}${colors.RESET}`;
}
return `${colors.LIGHT_GRAY}${branch} ${sep} ${dirPath} ${sep} ${modelName}${colors.RESET}`;
}
function buildSecondLine(
tokensUsed: number,
tokensMax: number,
contextPercentage: number,
fiveHourUtilization: number | null,
fiveHourReset: string | null,
sevenDayUtilization: number | null,
sevenDayReset: string | null,
sessionConfig: StatuslineConfig["session"],
limitsConfig: StatuslineConfig["limits"],
useIconLabels: boolean,
separator: string,
): string {
let line = formatSession(
tokensUsed,
tokensMax,
contextPercentage,
sessionConfig,
useIconLabels,
);
if (fiveHourUtilization !== null && fiveHourReset) {
const resetTime = formatResetTime(fiveHourReset);
const sep = `${colors.GRAY}${separator}`;
const label = useIconLabels
? `${colors.DIM}${symbols.USAGE_5H}${colors.RESET}`
: `${colors.DIM}5h:${colors.RESET}`;
if (limitsConfig.showProgressBar) {
const bar = formatProgressBar(
fiveHourUtilization,
limitsConfig.progressBarLength,
limitsConfig.color,
);
line += ` ${sep} ${label} ${bar} ${colors.LIGHT_GRAY}${fiveHourUtilization}${colors.GRAY}%${colors.RESET} ${colors.DIM}(${resetTime} left)${colors.RESET}`;
} else {
line += ` ${sep} ${label} ${colors.LIGHT_GRAY}${fiveHourUtilization}${colors.GRAY}%${colors.RESET} ${colors.DIM}(${resetTime} left)${colors.RESET}`;
}
}
if (
limitsConfig.showSevenDay &&
sevenDayUtilization !== null &&
sevenDayReset
) {
const resetTime = formatResetTime(sevenDayReset);
const sep = `${colors.GRAY}${separator}`;
const label = useIconLabels
? `${colors.DIM}${symbols.USAGE_7D}${colors.RESET}`
: `${colors.DIM}7d:${colors.RESET}`;
if (limitsConfig.showProgressBar) {
const bar = formatProgressBar(
sevenDayUtilization,
limitsConfig.progressBarLength,
limitsConfig.color,
);
line += ` ${sep} ${label} ${bar} ${colors.LIGHT_GRAY}${sevenDayUtilization}${colors.GRAY}%${colors.RESET} ${colors.DIM}(${resetTime} left)${colors.RESET}`;
} else {
line += ` ${sep} ${label} ${colors.LIGHT_GRAY}${sevenDayUtilization}${colors.GRAY}%${colors.RESET} ${colors.DIM}(${resetTime} left)${colors.RESET}`;
}
}
line += colors.RESET;
return line;
}
async function main() {
// Platform check: macOS only
if (process.platform !== "darwin") {
console.log(
`${colors.RED}Error:${colors.LIGHT_GRAY} cc-statusline requires macOS${colors.RESET}`,
);
console.log(
`${colors.GRAY}Current platform: ${process.platform}${colors.RESET}`,
);
process.exit(1);
}
try {
const config = await loadConfig();
const input: HookInput = await Bun.stdin.json();
const git = await getGitStatus();
const branch = formatBranch(git, config.git);
const dirPath = formatPath(
input.workspace.current_dir,
config.pathDisplayMode,
);
const contextData = await getContextData({
transcriptPath: input.transcript_path,
maxContextTokens: config.context.maxContextTokens,
autocompactBufferTokens: config.context.autocompactBufferTokens,
useUsableContextOnly: config.context.useUsableContextOnly,
overheadTokens: config.context.overheadTokens,
});
const usageLimits = await getUsageLimits();
const firstLine = buildFirstLine(
branch,
dirPath,
input.model.display_name,
config.showSonnetModel,
config.separator,
);
const secondLine = buildSecondLine(
contextData.tokens,
config.context.maxContextTokens,
contextData.percentage,
usageLimits.five_hour?.utilization ?? null,
usageLimits.five_hour?.resets_at ?? null,
usageLimits.seven_day?.utilization ?? null,
usageLimits.seven_day?.resets_at ?? null,
config.session,
config.limits,
config.useIconLabels,
config.separator,
);
if (!config.showFirstLine) {
// Only show second line (metrics)
console.log(secondLine);
console.log(""); // Empty second line for spacing
} else if (config.oneLine) {
// Show both lines merged
const sep = ` ${colors.GRAY}${config.separator}${colors.LIGHT_GRAY} `;
console.log(`${firstLine}${sep}${secondLine}`);
console.log(""); // Empty second line for spacing
} else {
// Show both lines separately
console.log(firstLine);
console.log(secondLine);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.log(
`${colors.RED}Error:${colors.LIGHT_GRAY} ${errorMessage}${colors.RESET}`,
);
console.log(`${colors.GRAY}Check statusline configuration${colors.RESET}`);
}
}
main();