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
26 changes: 13 additions & 13 deletions src/commands/blocks.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import process from 'node:process';
import { define } from 'gunshi';
import pc from 'picocolors';
import { getDefaultClaudePath, loadFiveHourBlockData } from '../data-loader.ts';
import { getDefaultClaudePath, loadSessionBlockData } from '../data-loader.ts';
import { log, logger } from '../logger.ts';
import {
calculateBurnRate,
filterRecentBlocks,
type FiveHourBlock,
projectBlockUsage,
} from '../five-hour-blocks.internal.ts';
import { log, logger } from '../logger.ts';
type SessionBlock,
} from '../session-blocks.internal.ts';
import { sharedCommandConfig } from '../shared-args.internal.ts';
import { formatCurrency, formatModelsDisplay, formatNumber } from '../utils.internal.ts';
import { ResponsiveTable } from '../utils.table.ts';
Expand All @@ -19,7 +19,7 @@ const WARNING_THRESHOLD = 0.8;
const COMPACT_WIDTH_THRESHOLD = 120;
const DEFAULT_TERMINAL_WIDTH = 120;

function formatBlockTime(block: FiveHourBlock, compact = false): string {
function formatBlockTime(block: SessionBlock, compact = false): string {
const start = compact
? block.startTime.toLocaleString(undefined, {
month: '2-digit',
Expand Down Expand Up @@ -91,7 +91,7 @@ function parseTokenLimit(value: string | undefined, maxFromAll: number): number

export const blocksCommand = define({
name: 'blocks',
description: 'Show usage report grouped by 5-hour billing blocks',
description: 'Show usage report grouped by session billing blocks',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider adding a more detailed description to clarify what constitutes a 'session billing block'.

description: 'Show usage report grouped by session billing blocks, which represent a 5-hour period of activity'

args: {
...sharedCommandConfig.args,
active: {
Expand All @@ -118,7 +118,7 @@ export const blocksCommand = define({
logger.level = 0;
}

let blocks = await loadFiveHourBlockData({
let blocks = await loadSessionBlockData({
since: ctx.values.since,
until: ctx.values.until,
claudePath: getDefaultClaudePath(),
Expand Down Expand Up @@ -158,13 +158,13 @@ export const blocksCommand = define({
}

if (ctx.values.active) {
blocks = blocks.filter((block: FiveHourBlock) => block.isActive);
blocks = blocks.filter((block: SessionBlock) => block.isActive);
if (blocks.length === 0) {
if (ctx.values.json) {
log(JSON.stringify({ blocks: [], message: 'No active block' }));
}
else {
logger.info('No active 5-hour block found.');
logger.info('No active session block found.');
}
process.exit(0);
}
Expand All @@ -173,7 +173,7 @@ export const blocksCommand = define({
if (ctx.values.json) {
// JSON output
const jsonOutput = {
blocks: blocks.map((block: FiveHourBlock) => {
blocks: blocks.map((block: SessionBlock) => {
const burnRate = block.isActive ? calculateBurnRate(block) : null;
const projection = block.isActive ? projectBlockUsage(block) : null;

Expand Down Expand Up @@ -218,15 +218,15 @@ export const blocksCommand = define({
// Table output
if (ctx.values.active && blocks.length === 1) {
// Detailed active block view
const block = blocks[0] as FiveHourBlock;
const block = blocks[0] as SessionBlock;
if (block == null) {
logger.warn('No active block found.');
process.exit(0);
}
const burnRate = calculateBurnRate(block);
const projection = projectBlockUsage(block);

logger.box('Current 5-Hour Block Status');
logger.box('Current Session Block Status');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider adding a brief explanation of what a 'Session Block' represents in the logger box.

logger.box('Current Session Block Status (5-hour billing cycle)')


const now = new Date();
const elapsed = Math.round(
Expand Down Expand Up @@ -279,7 +279,7 @@ export const blocksCommand = define({
}
else {
// Table view for multiple blocks
logger.box('Claude Code Token Usage Report - 5-Hour Blocks');
logger.box('Claude Code Token Usage Report - Session Blocks');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider adding a brief explanation of what 'Session Blocks' represents in the logger box.

logger.box('Claude Code Token Usage Report - Session Blocks (5-hour billing cycles)')


// Calculate token limit if "max" is specified
const actualTokenLimit = parseTokenLimit(ctx.values.tokenLimit, maxTokensFromAll);
Expand Down
24 changes: 12 additions & 12 deletions src/data-loader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
formatDateCompact,
getDefaultClaudePath,
loadDailyUsageData,
loadFiveHourBlockData,
loadMonthlyUsageData,
loadSessionBlockData,
loadSessionData,
type UsageData,
} from './data-loader.ts';
Expand Down Expand Up @@ -1940,10 +1940,10 @@ describe('calculateCostForEntry', () => {
});
});

describe('loadFiveHourBlockData', () => {
describe('loadSessionBlockData', () => {
test('returns empty array when no files found', async () => {
await using fixture = await createFixture({ projects: {} });
const result = await loadFiveHourBlockData({ claudePath: fixture.path });
const result = await loadSessionBlockData({ claudePath: fixture.path });
expect(result).toEqual([]);
});

Expand Down Expand Up @@ -2005,7 +2005,7 @@ describe('loadFiveHourBlockData', () => {
},
});

const result = await loadFiveHourBlockData({ claudePath: fixture.path });
const result = await loadSessionBlockData({ claudePath: fixture.path });
expect(result.length).toBeGreaterThan(0); // Should have blocks
expect(result[0]?.entries).toHaveLength(1); // First block has one entry
// Total entries across all blocks should be 3
Expand Down Expand Up @@ -2040,15 +2040,15 @@ describe('loadFiveHourBlockData', () => {
});

// Test display mode
const displayResult = await loadFiveHourBlockData({
const displayResult = await loadSessionBlockData({
claudePath: fixture.path,
mode: 'display',
});
expect(displayResult).toHaveLength(1);
expect(displayResult[0]?.costUSD).toBe(0.01);

// Test calculate mode
const calculateResult = await loadFiveHourBlockData({
const calculateResult = await loadSessionBlockData({
claudePath: fixture.path,
mode: 'calculate',
});
Expand Down Expand Up @@ -2106,15 +2106,15 @@ describe('loadFiveHourBlockData', () => {
});

// Test filtering with since parameter
const sinceResult = await loadFiveHourBlockData({
const sinceResult = await loadSessionBlockData({
claudePath: fixture.path,
since: '20240102',
});
expect(sinceResult.length).toBeGreaterThan(0);
expect(sinceResult.every(block => block.startTime >= date2)).toBe(true);

// Test filtering with until parameter
const untilResult = await loadFiveHourBlockData({
const untilResult = await loadSessionBlockData({
claudePath: fixture.path,
until: '20240102',
});
Expand Down Expand Up @@ -2164,14 +2164,14 @@ describe('loadFiveHourBlockData', () => {
});

// Test ascending order
const ascResult = await loadFiveHourBlockData({
const ascResult = await loadSessionBlockData({
claudePath: fixture.path,
order: 'asc',
});
expect(ascResult[0]?.startTime).toEqual(date1);

// Test descending order
const descResult = await loadFiveHourBlockData({
const descResult = await loadSessionBlockData({
claudePath: fixture.path,
order: 'desc',
});
Expand Down Expand Up @@ -2215,7 +2215,7 @@ describe('loadFiveHourBlockData', () => {
},
});

const result = await loadFiveHourBlockData({ claudePath: fixture.path });
const result = await loadSessionBlockData({ claudePath: fixture.path });
expect(result).toHaveLength(1);
expect(result[0]?.entries).toHaveLength(1); // Only one entry after deduplication
});
Expand Down Expand Up @@ -2247,7 +2247,7 @@ describe('loadFiveHourBlockData', () => {
},
});

const result = await loadFiveHourBlockData({ claudePath: fixture.path });
const result = await loadSessionBlockData({ claudePath: fixture.path });
expect(result).toHaveLength(1);
expect(result[0]?.entries).toHaveLength(1);
});
Expand Down
18 changes: 9 additions & 9 deletions src/data-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import { sort } from 'fast-sort';
import { isDirectorySync } from 'path-type';
import { glob } from 'tinyglobby';
import * as v from 'valibot';
import {
type FiveHourBlock,
identifyFiveHourBlocks,
type LoadedUsageEntry,
} from './five-hour-blocks.internal.ts';
import { logger } from './logger.ts';
import {
PricingFetcher,
} from './pricing-fetcher.ts';
import {
identifySessionBlocks,
type LoadedUsageEntry,
type SessionBlock,
} from './session-blocks.internal.ts';

const DEFAULT_CLAUDE_CODE_PATH = path.join(homedir(), '.claude');

Expand Down Expand Up @@ -820,9 +820,9 @@ export async function loadMonthlyUsageData(
return sortByDate(monthlyArray, item => `${item.month}-01`, options?.order);
}

export async function loadFiveHourBlockData(
export async function loadSessionBlockData(
options?: LoadOptions,
): Promise<FiveHourBlock[]> {
): Promise<SessionBlock[]> {
const claudePath = options?.claudePath ?? getDefaultClaudePath();
const claudeDir = path.join(claudePath, 'projects');
const files = await glob(['**/*.jsonl'], {
Expand Down Expand Up @@ -899,8 +899,8 @@ export async function loadFiveHourBlockData(
}
}

// Identify 5-hour blocks
const blocks = identifyFiveHourBlocks(allEntries);
// Identify session blocks
const blocks = identifySessionBlocks(allEntries);

// Filter by date range if specified
const filtered = (options?.since != null && options.since !== '') || (options?.until != null && options.until !== '')
Expand Down
6 changes: 3 additions & 3 deletions src/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { name, version } from '../package.json';
import {
getDefaultClaudePath,
loadDailyUsageData,
loadFiveHourBlockData,
loadMonthlyUsageData,
loadSessionBlockData,
loadSessionData,
} from './data-loader.ts';
import { CostModes, dateSchema } from './types.internal.ts';
Expand Down Expand Up @@ -88,10 +88,10 @@ export function createMcpServer({

server.addTool({
name: 'blocks',
description: 'Show usage report grouped by 5-hour billing blocks',
description: 'Show usage report grouped by session billing blocks',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider adding a more detailed description to clarify what constitutes a 'session billing block'.

description: 'Show usage report grouped by session billing blocks, representing a 5-hour period of activity'

parameters: parametersSchema,
execute: async (args) => {
const blocksData = await loadFiveHourBlockData({ ...args, claudePath });
const blocksData = await loadSessionBlockData({ ...args, claudePath });
return JSON.stringify(blocksData);
},
});
Expand Down
Loading