Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

**Testing and Quality:**

- `bun run test` - Run all tests (using vitest)
- `bun run test` - Run all tests (using vitest via bun)
- Lint code using ESLint MCP server (available via Claude Code tools)
- `bun run format` - Format code with ESLint (writes changes)
- `bun typecheck` - Type check with TypeScript
Expand Down Expand Up @@ -95,13 +95,19 @@ This is a CLI tool that analyzes Claude Code usage data from local JSONL files s
- Error handling: silently skips malformed JSONL lines during parsing
- File paths always use Node.js path utilities for cross-platform compatibility

**Naming Conventions:**

- Variables: start with lowercase (camelCase) - e.g., `usageDataSchema`, `modelBreakdownSchema`
- Types: start with uppercase (PascalCase) - e.g., `UsageData`, `ModelBreakdown`
- Constants: can use UPPER_SNAKE_CASE - e.g., `DEFAULT_CLAUDE_CODE_PATH`

**Post-Code Change Workflow:**

After making any code changes, ALWAYS run these commands in parallel:

- `bun run format` - Auto-fix and format code with ESLint (includes linting)
- `bun typecheck` - Type check with TypeScript
- `bun test` - Run all tests
- `bun run test` - Run all tests

This ensures code quality and catches issues immediately after changes.

Expand Down
32 changes: 16 additions & 16 deletions src/data-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Please set CLAUDE_CONFIG_DIR to a valid path, or ensure ${DEFAULT_CLAUDE_CODE_PA
/**
* Valibot schema for validating Claude usage data from JSONL files
*/
export const UsageDataSchema = v.object({
export const usageDataSchema = v.object({
timestamp: v.string(),
version: v.optional(v.string()), // Claude Code version
message: v.object({
Expand All @@ -64,12 +64,12 @@ export const UsageDataSchema = v.object({
/**
* Type definition for Claude usage data entries from JSONL files
*/
export type UsageData = v.InferOutput<typeof UsageDataSchema>;
export type UsageData = v.InferOutput<typeof usageDataSchema>;

/**
* Valibot schema for model-specific usage breakdown data
*/
export const ModelBreakdownSchema = v.object({
export const modelBreakdownSchema = v.object({
modelName: v.string(),
inputTokens: v.number(),
outputTokens: v.number(),
Expand All @@ -81,12 +81,12 @@ export const ModelBreakdownSchema = v.object({
/**
* Type definition for model-specific usage breakdown
*/
export type ModelBreakdown = v.InferOutput<typeof ModelBreakdownSchema>;
export type ModelBreakdown = v.InferOutput<typeof modelBreakdownSchema>;

/**
* Valibot schema for daily usage aggregation data
*/
export const DailyUsageSchema = v.object({
export const dailyUsageSchema = v.object({
date: v.pipe(
v.string(),
v.regex(/^\d{4}-\d{2}-\d{2}$/), // YYYY-MM-DD format
Expand All @@ -97,18 +97,18 @@ export const DailyUsageSchema = v.object({
cacheReadTokens: v.number(),
totalCost: v.number(),
modelsUsed: v.array(v.string()),
modelBreakdowns: v.array(ModelBreakdownSchema),
modelBreakdowns: v.array(modelBreakdownSchema),
});

/**
* Type definition for daily usage aggregation
*/
export type DailyUsage = v.InferOutput<typeof DailyUsageSchema>;
export type DailyUsage = v.InferOutput<typeof dailyUsageSchema>;

/**
* Valibot schema for session-based usage aggregation data
*/
export const SessionUsageSchema = v.object({
export const sessionUsageSchema = v.object({
sessionId: v.string(),
projectPath: v.string(),
inputTokens: v.number(),
Expand All @@ -119,18 +119,18 @@ export const SessionUsageSchema = v.object({
lastActivity: v.string(),
versions: v.array(v.string()), // List of unique versions used in this session
modelsUsed: v.array(v.string()),
modelBreakdowns: v.array(ModelBreakdownSchema),
modelBreakdowns: v.array(modelBreakdownSchema),
});

/**
* Type definition for session-based usage aggregation
*/
export type SessionUsage = v.InferOutput<typeof SessionUsageSchema>;
export type SessionUsage = v.InferOutput<typeof sessionUsageSchema>;

/**
* Valibot schema for monthly usage aggregation data
*/
export const MonthlyUsageSchema = v.object({
export const monthlyUsageSchema = v.object({
month: v.pipe(
v.string(),
v.regex(/^\d{4}-\d{2}$/), // YYYY-MM format
Expand All @@ -141,13 +141,13 @@ export const MonthlyUsageSchema = v.object({
cacheReadTokens: v.number(),
totalCost: v.number(),
modelsUsed: v.array(v.string()),
modelBreakdowns: v.array(ModelBreakdownSchema),
modelBreakdowns: v.array(modelBreakdownSchema),
});

/**
* Type definition for monthly usage aggregation
*/
export type MonthlyUsage = v.InferOutput<typeof MonthlyUsageSchema>;
export type MonthlyUsage = v.InferOutput<typeof monthlyUsageSchema>;

/**
* Internal type for aggregating token statistics and costs
Expand Down Expand Up @@ -586,7 +586,7 @@ export async function loadDailyUsageData(
for (const line of lines) {
try {
const parsed = JSON.parse(line) as unknown;
const result = v.safeParse(UsageDataSchema, parsed);
const result = v.safeParse(usageDataSchema, parsed);
if (!result.success) {
continue;
}
Expand Down Expand Up @@ -726,7 +726,7 @@ export async function loadSessionData(
for (const line of lines) {
try {
const parsed = JSON.parse(line) as unknown;
const result = v.safeParse(UsageDataSchema, parsed);
const result = v.safeParse(usageDataSchema, parsed);
if (!result.success) {
continue;
}
Expand Down Expand Up @@ -944,7 +944,7 @@ export async function loadSessionBlockData(
for (const line of lines) {
try {
const parsed = JSON.parse(line) as unknown;
const result = v.safeParse(UsageDataSchema, parsed);
const result = v.safeParse(usageDataSchema, parsed);
if (!result.success) {
continue;
}
Expand Down
4 changes: 2 additions & 2 deletions src/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import path from 'node:path';
import { createFixture } from 'fs-fixture';
import { glob } from 'tinyglobby';
import * as v from 'valibot';
import { UsageDataSchema } from './data-loader.ts';
import { usageDataSchema } from './data-loader.ts';
import { logger } from './logger.ts';
import { PricingFetcher } from './pricing-fetcher.ts';

Expand Down Expand Up @@ -98,7 +98,7 @@ export async function detectMismatches(
for (const line of lines) {
try {
const parsed = JSON.parse(line) as unknown;
const result = v.safeParse(UsageDataSchema, parsed);
const result = v.safeParse(usageDataSchema, parsed);

if (!result.success) {
continue;
Expand Down
4 changes: 2 additions & 2 deletions src/macro.internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import * as v from 'valibot';
import { LITELLM_PRICING_URL } from './consts.internal.ts';
import { type ModelPricing, ModelPricingSchema } from './types.internal.ts';
import { type ModelPricing, modelPricingSchema } from './types.internal.ts';

/**
* Prefetches the pricing data for Claude models from the LiteLLM API.
Expand All @@ -27,7 +27,7 @@ export async function prefetchClaudePricing(): Promise<Record<string, ModelPrici
// Cache all models that start with 'claude-'
for (const [modelName, modelData] of Object.entries(data)) {
if (modelName.startsWith('claude-') && modelData != null && typeof modelData === 'object') {
const parsed = v.safeParse(ModelPricingSchema, modelData);
const parsed = v.safeParse(modelPricingSchema, modelData);
if (parsed.success) {
prefetchClaudeData[modelName] = parsed.output;
}
Expand Down
4 changes: 2 additions & 2 deletions src/pricing-fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as v from 'valibot';
import { LITELLM_PRICING_URL } from './consts.internal.ts';
import { logger } from './logger.ts';
import { prefetchClaudePricing } from './macro.internal.ts' with { type: 'macro' };
import { type ModelPricing, ModelPricingSchema } from './types.internal.ts';
import { type ModelPricing, modelPricingSchema } from './types.internal.ts';

/**
* Fetches and caches model pricing information from LiteLLM
Expand Down Expand Up @@ -64,7 +64,7 @@ export class PricingFetcher implements Disposable {
data as Record<string, unknown>,
)) {
if (typeof modelData === 'object' && modelData !== null) {
const parsed = v.safeParse(ModelPricingSchema, modelData);
const parsed = v.safeParse(modelPricingSchema, modelData);
if (parsed.success) {
pricing.set(modelName, parsed.output);
}
Expand Down
4 changes: 2 additions & 2 deletions src/types.internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export type SortOrder = TupleToUnion<typeof SortOrders>;
/**
* Valibot schema for model pricing information from LiteLLM
*/
export const ModelPricingSchema = v.object({
export const modelPricingSchema = v.object({
input_cost_per_token: v.optional(v.number()),
output_cost_per_token: v.optional(v.number()),
cache_creation_input_token_cost: v.optional(v.number()),
Expand All @@ -45,4 +45,4 @@ export const ModelPricingSchema = v.object({
/**
* Type definition for model pricing information
*/
export type ModelPricing = v.InferOutput<typeof ModelPricingSchema>;
export type ModelPricing = v.InferOutput<typeof modelPricingSchema>;