feat(ccusage): add hourly usage report by humanwritten · Pull Request #724 · ccusage/ccusage · GitHub
Skip to content

feat(ccusage): add hourly usage report#724

Closed
humanwritten wants to merge 3 commits into
ccusage:mainfrom
humanwritten:feat/hourly-usage-report-fixed
Closed

feat(ccusage): add hourly usage report#724
humanwritten wants to merge 3 commits into
ccusage:mainfrom
humanwritten:feat/hourly-usage-report-fixed

Conversation

@humanwritten

@humanwritten humanwritten commented Nov 15, 2025

Copy link
Copy Markdown

PR Notes: Add Hourly Usage Report

Summary

Adds a new hourly command to ccusage that displays usage data aggregated by hour for the last 24 hours.

Changes Made

New Files

  • apps/ccusage/src/commands/hourly.ts (238 lines)
    • New CLI command with full argument support
    • Supports all standard flags: --instances, --project, --json, --compact, --breakdown, --timezone, --locale, --jq
    • New flag: --today / -t to show only hours from current calendar day
    • Table and JSON output modes with totals

Modified Files

  1. apps/ccusage/src/commands/index.ts

    • Added hourlyCommand to exports
    • Registered in subCommandUnion array
  2. apps/ccusage/src/data-loader.ts

    • New function: loadHourlyUsageData() (lines 1087-1257)
    • Comprehensive JSDoc with hour-to-day alignment documentation
    • 6 new in-source tests (lines 2767-3038)
    • Total test count: 263 → 269 tests

Test Coverage

Tests Added (6 new tests in data-loader.ts)

  1. Basic hourly aggregation - Verifies multiple entries in same hour are summed correctly
  2. 24-hour window filtering - Ensures data older than 24 hours is excluded
  3. Empty data handling - Returns empty array when no data found
  4. Timezone support - Verifies timezone option is respected
  5. Project grouping - Tests --instances flag functionality
  6. Hourly-daily alignment ⭐ - Critical test that mathematically proves hourly totals match daily totals

Test Results

✓ All 269 tests passing
✓ Format check passed
✓ Type check passed
✓ ESLint passed

Hour-to-Day Alignment

Documentation Added

Comprehensive JSDoc explaining how hours map to days:

  • Hours formatted using same timezone logic as daily reports
  • All hours 00:00-23:00 in configured timezone sum to that day's total
  • Example: UTC hours "2024-01-15 00:00" through "2024-01-15 23:00" = daily total for "2024-01-15"

Test Verification

The alignment test (lines 2944-3038) proves:

  • Sum of hourly input tokens = daily input tokens ✓
  • Sum of hourly output tokens = daily output tokens ✓
  • Sum of hourly cache tokens = daily cache tokens ✓
  • Sum of hourly costs = daily costs ✓

Real-World Verification

Manually tested with actual Claude usage data:

# Hourly for today
Input: 108,674 tokens
Output: 441,245 tokens
Cost: $75.19

# Daily for 2025-11-14
Input: 108,674 tokens  ✅ EXACT MATCH
Output: 441,357 tokens  ≈  (new usage between commands)
Cost: $75.23          ≈

Key Features

  1. Last 24 Hours Window - Filters data to only include entries from last 24 hours
  2. Timezone Awareness - Uses same timezone logic as daily reports for consistency
  3. Hour Formatting - YYYY-MM-DD HH:00 format matching user's timezone
  4. Project Grouping - Optional --instances flag for per-project breakdown
  5. Today Filter - New --today flag to show only current day's hours
  6. Deduplication - Same hash-based deduplication as other commands
  7. Cost Modes - Supports auto, calculate, and display modes
  8. Ascending Sort - Hours sorted chronologically (oldest to newest)

Usage Examples

# Basic usage - last 24 hours
npx ccusage hourly

# Show only today's hours
npx ccusage hourly --today

# Group by project
npx ccusage hourly --instances

# JSON output
npx ccusage hourly --json

# With jq processing
npx ccusage hourly --jq '.totals.totalCost'

# Compact mode (for screenshots)
npx ccusage hourly --compact

# With timezone
npx ccusage hourly --timezone America/New_York

# Combine flags
npx ccusage hourly --today --instances --breakdown

Code Quality

Follows Project Standards

  • ✅ In-source testing pattern (all tests in data-loader.ts)
  • ✅ No separate .test.ts files (matches repo convention)
  • ✅ Command layer has no tests (same as daily.ts, monthly.ts, weekly.ts)
  • ✅ Uses existing helper functions (aggregateByModel, calculateTotals, etc.)
  • ✅ Proper error handling with Result types
  • ✅ ESLint compliant with proper return type annotations
  • ✅ TypeScript strict mode compatible

Pattern Consistency

  • Mirrors loadWeeklyUsageData() structure
  • Uses same deduplication logic as other loaders
  • Follows same timezone/locale handling pattern
  • Consistent table formatting with other commands

Commit Message Suggestion

feat(ccusage): add hourly usage report

- Add new `hourly` command showing last 24 hours of usage
- Group usage data into hourly buckets (YYYY-MM-DD HH:00)
- Support timezone-aware hour formatting
- Add --today flag to filter to current calendar day
- Add project grouping with --instances flag
- Include 6 comprehensive tests verifying functionality
- Document hour-to-day alignment for data consistency
- Support all standard output modes (table, JSON, compact)

Files Changed Summary

apps/ccusage/src/commands/index.ts     |   4 +-
apps/ccusage/src/commands/hourly.ts    | 238 +++++++++++++++++++
apps/ccusage/src/data-loader.ts        | 279 +++++++++++++++++++++-
3 files changed, 519 insertions(+), 2 deletions(-)

Pre-PR Checklist

  • Code follows project style guidelines
  • All tests passing (269/269)
  • ESLint passing
  • TypeScript type checking passing
  • Formatting check passing
  • Comprehensive in-source tests added
  • Documentation with hour-to-day alignment
  • Manually tested with real data
  • Follows existing command patterns
  • No breaking changes

Additional Notes

Why --today is Not Tested

  • Command-layer code has no tests in this repo (established pattern)
  • daily.ts, monthly.ts, weekly.ts, blocks.ts have zero tests
  • The --today flag is 8 lines of simple filtering logic
  • Manually verified to work correctly with real data
  • Follows established repo testing conventions

Why This Matters

The hourly report fills a gap between session-level detail and daily aggregation:

  • Session reports - Too granular for quick overview
  • Daily reports - Not granular enough to see patterns within a day
  • Hourly reports - Perfect for seeing usage patterns throughout the day

Users can now:

  1. Identify peak usage hours
  2. Correlate costs with specific work sessions
  3. Verify daily totals by summing hours
  4. Monitor real-time usage with --today

Testing the PR

# Clone and install
git clone <your-fork>
cd ccusage
pnpm install

# Run tests
pnpm run format
pnpm typecheck
pnpm run test

# Try the command
cd apps/ccusage
pnpm run start hourly
pnpm run start hourly --today
pnpm run start hourly --instances
pnpm run start hourly --json

Ready for review! 🚀

Summary by CodeRabbit

  • New Features

    • Added an "hourly" usage report: 24‑hour hourly breakdowns with totals, per‑model cost breakdowns, JSON output with optional jq processing, human‑readable table display, optional per‑project grouping with project aliases, timezone/"today" filtering, compact display guidance, and an optional debug mismatch report.
  • Documentation

    • Configuration schema extended to include hourly command options for display, filtering, and formatting.

@coderabbitai

coderabbitai Bot commented Nov 15, 2025

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
apps/ccusage/src/data-loader.ts (1)

1148-1165: Avoid relying on non‑ISO date parsing when sorting hourly buckets

formatHourKey generates strings like "YYYY-MM-DD HH:00" and sortByDate later parses these via new Date(getDate(item)). That format isn’t ISO‑8601, so parsing is implementation‑dependent and could break on non‑Node runtimes or future engines.

Since the hour strings are already lexicographically sortable, you can avoid parsing entirely:

-	// Sort ascending by hour for a chronological 24h view
-	return sortByDate(results, item => item.hour, 'asc');
+	// Sort ascending by hour for a chronological 24h view.
+	// Hour strings are in "YYYY-MM-DD HH:00" format, so lexicographic order matches chronological order.
+	return results.sort((a, b) => a.hour.localeCompare(b.hour));

Additionally, you recreate Intl.DateTimeFormat on every formatHourKey call. For large datasets, consider hoisting it once per invocation:

-	// Helper to format hour key with timezone awareness
-	function formatHourKey(timestamp: string): string {
-		const d = new Date(timestamp);
-		// Use Intl with timezone if provided to extract local parts
-		const formatter = new Intl.DateTimeFormat(options?.locale ?? DEFAULT_LOCALE, {
+	// Helper to format hour key with timezone awareness
+	const hourFormatter = new Intl.DateTimeFormat(options?.locale ?? DEFAULT_LOCALE, {
 			year: 'numeric',
 			month: '2-digit',
 			day: '2-digit',
 			hour: '2-digit',
 			hour12: false,
 			timeZone: options?.timezone,
-		});
+	});
+
+	function formatHourKey(timestamp: string): string {
+		const d = new Date(timestamp);
 		const parts = formatter.formatToParts(d);
 		const year = parts.find(p => p.type === 'year')?.value ?? '';
 		const month = parts.find(p => p.type === 'month')?.value ?? '';
 		const day = parts.find(p => p.type === 'day')?.value ?? '';
 		const hour = parts.find(p => p.type === 'hour')?.value ?? '00';
 		return `${year}-${month}-${day} ${hour}:00`;
 	}

Also applies to: 1262-1263

apps/ccusage/src/commands/hourly.ts (2)

81-93: Use shared default locale constant instead of hard‑coding 'en-CA'

The --today filter builds todayDate using Intl.DateTimeFormat(mergedOptions.locale ?? 'en-CA', …), while the loader and other code paths rely on DEFAULT_LOCALE from _consts.ts for consistent date handling.

To avoid drift if the default ever changes, consider switching to the shared constant:

-			const formatter = new Intl.DateTimeFormat(mergedOptions.locale ?? 'en-CA', {
+			const formatter = new Intl.DateTimeFormat(mergedOptions.locale ?? DEFAULT_LOCALE, {

(You’d need to import DEFAULT_LOCALE here for consistency with loadHourlyUsageData.)


187-203: Reuse addEmptySeparatorRow helper instead of manual empty rows

When separating project groups you currently push a literal ['', '', '', '', '', '', '', ''], while later you already use addEmptySeparatorRow(table, 8) for the totals separator.

For consistency and to avoid hard‑coding column counts in multiple places, you can reuse the helper:

-					if (!isFirstProject) {
-						table.push(['', '', '', '', '', '', '', '']);
-					}
+					if (!isFirstProject) {
+						addEmptySeparatorRow(table, 8);
+					}

This keeps separator rendering logic in one place.

Also applies to: 242-253

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0850376 and bd492d605fea2bf2743f62f139dce6daebe421fc.

📒 Files selected for processing (3)
  • apps/ccusage/src/commands/hourly.ts (1 hunks)
  • apps/ccusage/src/commands/index.ts (1 hunks)
  • apps/ccusage/src/data-loader.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
apps/ccusage/src/**/*.ts

📄 CodeRabbit inference engine (apps/ccusage/CLAUDE.md)

apps/ccusage/src/**/*.ts: Write tests in-source using if (import.meta.vitest != null) blocks instead of separate test files
Use Vitest globals (describe, it, expect) without imports in test blocks
In tests, use current Claude 4 models (sonnet-4, opus-4)
Use fs-fixture with createFixture() to simulate Claude data in tests
Only export symbols that are actually used by other modules
Do not use console.log; use the logger utilities from src/logger.ts instead

Files:

  • apps/ccusage/src/commands/hourly.ts
  • apps/ccusage/src/commands/index.ts
  • apps/ccusage/src/data-loader.ts
apps/ccusage/**/*.ts

📄 CodeRabbit inference engine (apps/ccusage/CLAUDE.md)

apps/ccusage/**/*.ts: NEVER use await import() dynamic imports anywhere (especially in tests)
Prefer @praha/byethrow Result type for error handling instead of try-catch
Use .ts extensions for local imports (e.g., import { foo } from './utils.ts')

Files:

  • apps/ccusage/src/commands/hourly.ts
  • apps/ccusage/src/commands/index.ts
  • apps/ccusage/src/data-loader.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Use tab indentation and double quotes (ESLint formatting)
Do not use console.log; only allow where explicitly disabled via eslint-disable
Always use Node.js path utilities for file paths for cross-platform compatibility
Use .ts extensions for local file imports (e.g., import { foo } from './utils.ts')
Prefer @praha/byethrow Result type over traditional try-catch for functional error handling
Use Result.try() to wrap operations that may throw (e.g., JSON parsing)
Use Result.isFailure() for checking errors instead of negating isSuccess()
Use early return on failures (e.g., if (Result.isFailure(r)) continue) instead of ternary patterns
For async operations, create a wrapper using Result.try() and call it
Keep traditional try-catch only for complex file I/O or legacy code that’s hard to refactor
Always use Result.isFailure() and Result.isSuccess() type guards for clarity
Variables use camelCase naming
Types use PascalCase naming
Constants can use UPPER_SNAKE_CASE
Only export constants, functions, and types that are actually used by other modules
Do not export internal/private constants that are only used within the same file
Before exporting a constant, verify it is referenced by other modules
Use Vitest globals (describe, it, expect) without imports in test blocks
Never use await import() dynamic imports anywhere in the codebase
Never use dynamic imports inside Vitest test blocks
Use fs-fixture createFixture() for mock Claude data directories in tests
All tests must use current Claude 4 models (not Claude 3)
Test coverage should include both Sonnet and Opus models
Model names in tests must exactly match LiteLLM pricing database entries
Use logger.ts instead of console.log for logging

Files:

  • apps/ccusage/src/commands/hourly.ts
  • apps/ccusage/src/commands/index.ts
  • apps/ccusage/src/data-loader.ts
**/data-loader.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Silently skip malformed JSONL lines during parsing in data-loader.ts

Files:

  • apps/ccusage/src/data-loader.ts
🧠 Learnings (5)
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/**/*.ts : Use `.ts` extensions for local imports (e.g., `import { foo } from './utils.ts'`)

Applied to files:

  • apps/ccusage/src/commands/index.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Use `fs-fixture` with `createFixture()` to simulate Claude data in tests

Applied to files:

  • apps/ccusage/src/data-loader.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : In tests, use current Claude 4 models (sonnet-4, opus-4)

Applied to files:

  • apps/ccusage/src/data-loader.ts
📚 Learning: 2025-09-18T17:43:09.255Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-18T17:43:09.255Z
Learning: Applies to **/*.ts : Use fs-fixture createFixture() for mock Claude data directories in tests

Applied to files:

  • apps/ccusage/src/data-loader.ts
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Tests should use fs-fixture with using to ensure cleanup

Applied to files:

  • apps/ccusage/src/data-loader.ts
🧬 Code graph analysis (3)
apps/ccusage/src/commands/hourly.ts (8)
apps/ccusage/src/_shared-args.ts (1)
  • sharedCommandConfig (116-119)
apps/ccusage/src/_config-loader-tokens.ts (1)
  • mergeConfigWithArgs (212-298)
apps/ccusage/src/data-loader.ts (1)
  • loadHourlyUsageData (1100-1264)
apps/ccusage/src/debug.ts (2)
  • detectMismatches (75-218)
  • printMismatchReport (225-309)
apps/ccusage/src/calculate-cost.ts (1)
  • createTotalsObject (77-82)
apps/ccusage/src/_jq-processor.ts (1)
  • processWithJq (10-36)
packages/terminal/src/table.ts (6)
  • UsageReportConfig (415-424)
  • createUsageReportTable (443-500)
  • formatUsageDataRow (509-532)
  • pushBreakdownRows (369-410)
  • addEmptySeparatorRow (566-569)
  • formatTotalsRow (540-559)
apps/ccusage/src/_project-names.ts (1)
  • formatProjectName (137-152)
apps/ccusage/src/commands/index.ts (1)
apps/ccusage/src/commands/hourly.ts (1)
  • hourlyCommand (19-264)
apps/ccusage/src/data-loader.ts (6)
apps/codex/src/data-loader.ts (1)
  • LoadOptions (169-171)
apps/ccusage/src/_types.ts (2)
  • ModelName (91-91)
  • createISOTimestamp (113-113)
apps/ccusage/src/_pricing-fetcher.ts (1)
  • PricingFetcher (16-25)
apps/ccusage/src/_consts.ts (1)
  • DEFAULT_LOCALE (148-148)
apps/ccusage/src/calculate-cost.ts (1)
  • calculateTotals (48-67)
apps/ccusage/src/_date-utils.ts (1)
  • sortByDate (94-108)
🔇 Additional comments (4)
apps/ccusage/src/data-loader.ts (2)

1100-1212: Hourly loader ignores since/until even though options carry them

loadHourlyUsageData always enforces a “last 24 hours from now” window and never applies options.since / options.until, even though LoadOptions includes those fields and the hourly command inherits the shared date-range args. This is fine if hourly is explicitly defined as “last 24h only”, but it means --since/--until will be silently ignored for this command.

Consider either:

  • Hiding/removing since/until from the hourly command’s args, or
  • Applying them as an additional filter on top of the 24h window, or
  • Documenting clearly in help text that they are ignored for hourly.

2774-3037: Hourly loader tests give good coverage and follow repo testing conventions

The loadHourlyUsageData tests cover aggregation, 24‑hour filtering, empty data, timezone option, per‑project grouping, and hourly vs daily alignment, and they use createFixture() and in‑source Vitest blocks as per guidelines. The use of relative timestamps (hours ago) keeps them robust against clock drift.

Looks solid as‑is.

apps/ccusage/src/commands/hourly.ts (1)

19-77: Hourly command wiring and JSON output look consistent with existing commands

The new hourly command integrates cleanly with the shared config/args pattern, handles --instances, --project, --today, --json, and --jq coherently, and correctly reuses loadHourlyUsageData, getTotalTokens, and createTotalsObject for JSON output. Debug mismatch reporting is also gated behind debug and non‑JSON mode.

No functional issues stand out here.

Also applies to: 128-164

apps/ccusage/src/commands/index.ts (1)

6-26: Hourly subcommand is correctly registered and exported

Importing hourlyCommand, adding it to the export list, and including ['hourly', hourlyCommand] in subCommandUnion keeps the registry and CommandName type in sync. The subCommands map will now correctly dispatch the new subcommand.

Looks good.

- Add new `hourly` command showing last 24 hours of usage
- Group usage data into hourly buckets (YYYY-MM-DD HH:00)
- Support timezone-aware hour formatting
- Add --today flag to filter to current calendar day
- Add project grouping with --instances flag
- Include 6 comprehensive tests verifying functionality
- Document hour-to-day alignment for data consistency
- Support all standard output modes (table, JSON, compact)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@humanwritten humanwritten force-pushed the feat/hourly-usage-report-fixed branch from bd492d6 to 27687a9 Compare November 15, 2025 05:33

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/ccusage/src/data-loader.ts (2)

1679-1679: Remove dynamic import in tests (repo rule: never use await import)

You already import createFixture at module top; drop this line.

Apply this diff:

-    const { createFixture } = await import('fs-fixture');

As per coding guidelines.


5133-5133: Remove dynamic import in tests (repo rule: never use await import)

Use the existing top‑level import instead.

Apply this diff:

-    const { createFixture } = await import('fs-fixture');

As per coding guidelines.

🧹 Nitpick comments (2)
apps/ccusage/src/data-loader.ts (1)

1148-1165: Avoid per‑call Intl.DateTimeFormat allocation in formatHourKey

Cache the formatter once per invocation; large logs benefit materially.

Apply this diff:

-  // Helper to format hour key with timezone awareness
-  function formatHourKey(timestamp: string): string {
-    const d = new Date(timestamp);
-    // Use Intl with timezone if provided to extract local parts
-    const formatter = new Intl.DateTimeFormat(options?.locale ?? DEFAULT_LOCALE, {
-      year: 'numeric',
-      month: '2-digit',
-      day: '2-digit',
-      hour: '2-digit',
-      hour12: false,
-      timeZone: options?.timezone,
-    });
-    const parts = formatter.formatToParts(d);
+  // Helper to format hour key with timezone awareness
+  const hourFormatter = new Intl.DateTimeFormat(options?.locale ?? DEFAULT_LOCALE, {
+    year: "numeric",
+    month: "2-digit",
+    day: "2-digit",
+    hour: "2-digit",
+    hour12: false,
+    timeZone: options?.timezone,
+  });
+  function formatHourKey(timestamp: string): string {
+    const d = new Date(timestamp);
+    const parts = hourFormatter.formatToParts(d);
     const year = parts.find(p => p.type === 'year')?.value ?? '';
     const month = parts.find(p => p.type === 'month')?.value ?? '';
     const day = parts.find(p => p.type === 'day')?.value ?? '';
     const hour = parts.find(p => p.type === 'hour')?.value ?? '00';
     return `${year}-${month}-${day} ${hour}:00`;
   }
apps/ccusage/src/commands/hourly.ts (1)

190-201: Use adaptive separator row (compact vs full table)

Avoid hard‑coding 8 empty cells; reuse helper so widths match both modes.

Apply this diff:

-          if (!isFirstProject) {
-            table.push(['', '', '', '', '', '', '', '']);
-          }
+          if (!isFirstProject) {
+            addEmptySeparatorRow(table, table.isCompactMode() ? 5 : 8);
+          }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd492d605fea2bf2743f62f139dce6daebe421fc and 27687a9.

📒 Files selected for processing (4)
  • apps/ccusage/config-schema.json (1 hunks)
  • apps/ccusage/src/commands/hourly.ts (1 hunks)
  • apps/ccusage/src/commands/index.ts (1 hunks)
  • apps/ccusage/src/data-loader.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
apps/ccusage/src/**/*.ts

📄 CodeRabbit inference engine (apps/ccusage/CLAUDE.md)

apps/ccusage/src/**/*.ts: Write tests in-source using if (import.meta.vitest != null) blocks instead of separate test files
Use Vitest globals (describe, it, expect) without imports in test blocks
In tests, use current Claude 4 models (sonnet-4, opus-4)
Use fs-fixture with createFixture() to simulate Claude data in tests
Only export symbols that are actually used by other modules
Do not use console.log; use the logger utilities from src/logger.ts instead

Files:

  • apps/ccusage/src/commands/hourly.ts
  • apps/ccusage/src/data-loader.ts
  • apps/ccusage/src/commands/index.ts
apps/ccusage/**/*.ts

📄 CodeRabbit inference engine (apps/ccusage/CLAUDE.md)

apps/ccusage/**/*.ts: NEVER use await import() dynamic imports anywhere (especially in tests)
Prefer @praha/byethrow Result type for error handling instead of try-catch
Use .ts extensions for local imports (e.g., import { foo } from './utils.ts')

Files:

  • apps/ccusage/src/commands/hourly.ts
  • apps/ccusage/src/data-loader.ts
  • apps/ccusage/src/commands/index.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Use tab indentation and double quotes (ESLint formatting)
Do not use console.log; only allow where explicitly disabled via eslint-disable
Always use Node.js path utilities for file paths for cross-platform compatibility
Use .ts extensions for local file imports (e.g., import { foo } from './utils.ts')
Prefer @praha/byethrow Result type over traditional try-catch for functional error handling
Use Result.try() to wrap operations that may throw (e.g., JSON parsing)
Use Result.isFailure() for checking errors instead of negating isSuccess()
Use early return on failures (e.g., if (Result.isFailure(r)) continue) instead of ternary patterns
For async operations, create a wrapper using Result.try() and call it
Keep traditional try-catch only for complex file I/O or legacy code that’s hard to refactor
Always use Result.isFailure() and Result.isSuccess() type guards for clarity
Variables use camelCase naming
Types use PascalCase naming
Constants can use UPPER_SNAKE_CASE
Only export constants, functions, and types that are actually used by other modules
Do not export internal/private constants that are only used within the same file
Before exporting a constant, verify it is referenced by other modules
Use Vitest globals (describe, it, expect) without imports in test blocks
Never use await import() dynamic imports anywhere in the codebase
Never use dynamic imports inside Vitest test blocks
Use fs-fixture createFixture() for mock Claude data directories in tests
All tests must use current Claude 4 models (not Claude 3)
Test coverage should include both Sonnet and Opus models
Model names in tests must exactly match LiteLLM pricing database entries
Use logger.ts instead of console.log for logging

Files:

  • apps/ccusage/src/commands/hourly.ts
  • apps/ccusage/src/data-loader.ts
  • apps/ccusage/src/commands/index.ts
**/data-loader.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Silently skip malformed JSONL lines during parsing in data-loader.ts

Files:

  • apps/ccusage/src/data-loader.ts
🧠 Learnings (5)
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Use `fs-fixture` with `createFixture()` to simulate Claude data in tests

Applied to files:

  • apps/ccusage/src/data-loader.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : In tests, use current Claude 4 models (sonnet-4, opus-4)

Applied to files:

  • apps/ccusage/src/data-loader.ts
📚 Learning: 2025-09-18T17:43:09.255Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-18T17:43:09.255Z
Learning: Applies to **/*.ts : Use fs-fixture createFixture() for mock Claude data directories in tests

Applied to files:

  • apps/ccusage/src/data-loader.ts
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Tests should use fs-fixture with using to ensure cleanup

Applied to files:

  • apps/ccusage/src/data-loader.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/**/*.ts : Use `.ts` extensions for local imports (e.g., `import { foo } from './utils.ts'`)

Applied to files:

  • apps/ccusage/src/commands/index.ts
🧬 Code graph analysis (3)
apps/ccusage/src/commands/hourly.ts (8)
apps/ccusage/src/_shared-args.ts (1)
  • sharedCommandConfig (116-119)
apps/ccusage/src/_config-loader-tokens.ts (2)
  • loadConfig (146-197)
  • mergeConfigWithArgs (212-298)
apps/ccusage/src/data-loader.ts (1)
  • loadHourlyUsageData (1100-1264)
apps/ccusage/src/debug.ts (2)
  • detectMismatches (75-218)
  • printMismatchReport (225-309)
apps/ccusage/src/calculate-cost.ts (1)
  • createTotalsObject (77-82)
apps/ccusage/src/_jq-processor.ts (1)
  • processWithJq (10-36)
packages/terminal/src/table.ts (6)
  • UsageReportConfig (415-424)
  • createUsageReportTable (443-500)
  • formatUsageDataRow (509-532)
  • pushBreakdownRows (369-410)
  • addEmptySeparatorRow (566-569)
  • formatTotalsRow (540-559)
apps/ccusage/src/_project-names.ts (1)
  • formatProjectName (137-152)
apps/ccusage/src/data-loader.ts (6)
apps/codex/src/data-loader.ts (1)
  • LoadOptions (169-171)
apps/ccusage/src/_types.ts (2)
  • ModelName (91-91)
  • createISOTimestamp (113-113)
apps/ccusage/src/_pricing-fetcher.ts (1)
  • PricingFetcher (16-25)
apps/ccusage/src/_consts.ts (1)
  • DEFAULT_LOCALE (148-148)
apps/ccusage/src/calculate-cost.ts (1)
  • calculateTotals (48-67)
apps/ccusage/src/_date-utils.ts (1)
  • sortByDate (94-108)
apps/ccusage/src/commands/index.ts (1)
apps/ccusage/src/commands/hourly.ts (1)
  • hourlyCommand (19-264)
🔇 Additional comments (3)
apps/ccusage/config-schema.json (1)

231-351: Hourly schema looks consistent with existing commands

Properties and defaults mirror daily/weekly; today flag is clearly documented. No issues.

apps/ccusage/src/commands/index.ts (1)

6-6: Wiring looks correct

Import/export and subCommandUnion entry for hourly are consistent.

Also applies to: 13-13, 20-21

apps/ccusage/src/data-loader.ts (1)

3400-3460: The review comment is factually incorrect.

The code at lines 3400–3460 uses only the correct canonical model names:

  • Line 3402: claude-sonnet-4-20250514
  • Line 3442: claude-opus-4-20250514

These models are confirmed in the LiteLLM documentation and exist in the bundled pricing. The alleged incorrect format claude-4-sonnet-20250514 does not appear anywhere in the codebase. LiteLLM may use "claude-4-sonnet" as an internal alias, but the underlying model ID is always anthropic/claude-sonnet-4-20250514, which matches the tests. No naming inconsistency or pricing risk exists.

Likely an incorrect or invalid review comment.

Comment thread apps/ccusage/src/data-loader.ts Outdated
Comment on lines +1262 to +1264
// Sort ascending by hour for a chronological 24h view
return sortByDate(results, item => item.hour, 'asc');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Respect the --order option (don’t hard‑code 'asc')

Hourly loader ignores options.order. Align with daily/weekly by honoring the flag.

Apply this diff:

-  // Sort ascending by hour for a chronological 24h view
-  return sortByDate(results, item => item.hour, 'asc');
+  // Sort by hour honoring --order (default to asc for hourly)
+  return sortByDate(results, item => item.hour, options?.order ?? 'asc');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Sort ascending by hour for a chronological 24h view
return sortByDate(results, item => item.hour, 'asc');
}
// Sort by hour honoring --order (default to asc for hourly)
return sortByDate(results, item => item.hour, options?.order ?? 'asc');
}
🤖 Prompt for AI Agents
In apps/ccusage/src/data-loader.ts around lines 1262-1264, the hourly loader
currently hardcodes the sort direction to 'asc' when calling sortByDate,
ignoring the CLI --order flag; change the call to use the incoming options.order
(falling back to 'asc' if undefined) so it respects the same order handling as
the daily/weekly loaders, and validate/normalize the value to either 'asc' or
'desc' before passing it to sortByDate.

- Remove dynamic imports (violates project rules)
- Respect --order flag in hourly loader
- Hoist DateTimeFormatter for performance
- Use DEFAULT_LOCALE constant for consistency
- Use addEmptySeparatorRow helper for cleaner code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27687a9 and d1588f0.

📒 Files selected for processing (2)
  • apps/ccusage/src/commands/hourly.ts (1 hunks)
  • apps/ccusage/src/data-loader.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
apps/ccusage/src/**/*.ts

📄 CodeRabbit inference engine (apps/ccusage/CLAUDE.md)

apps/ccusage/src/**/*.ts: Write tests in-source using if (import.meta.vitest != null) blocks instead of separate test files
Use Vitest globals (describe, it, expect) without imports in test blocks
In tests, use current Claude 4 models (sonnet-4, opus-4)
Use fs-fixture with createFixture() to simulate Claude data in tests
Only export symbols that are actually used by other modules
Do not use console.log; use the logger utilities from src/logger.ts instead

Files:

  • apps/ccusage/src/commands/hourly.ts
  • apps/ccusage/src/data-loader.ts
apps/ccusage/**/*.ts

📄 CodeRabbit inference engine (apps/ccusage/CLAUDE.md)

apps/ccusage/**/*.ts: NEVER use await import() dynamic imports anywhere (especially in tests)
Prefer @praha/byethrow Result type for error handling instead of try-catch
Use .ts extensions for local imports (e.g., import { foo } from './utils.ts')

Files:

  • apps/ccusage/src/commands/hourly.ts
  • apps/ccusage/src/data-loader.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Use tab indentation and double quotes (ESLint formatting)
Do not use console.log; only allow where explicitly disabled via eslint-disable
Always use Node.js path utilities for file paths for cross-platform compatibility
Use .ts extensions for local file imports (e.g., import { foo } from './utils.ts')
Prefer @praha/byethrow Result type over traditional try-catch for functional error handling
Use Result.try() to wrap operations that may throw (e.g., JSON parsing)
Use Result.isFailure() for checking errors instead of negating isSuccess()
Use early return on failures (e.g., if (Result.isFailure(r)) continue) instead of ternary patterns
For async operations, create a wrapper using Result.try() and call it
Keep traditional try-catch only for complex file I/O or legacy code that’s hard to refactor
Always use Result.isFailure() and Result.isSuccess() type guards for clarity
Variables use camelCase naming
Types use PascalCase naming
Constants can use UPPER_SNAKE_CASE
Only export constants, functions, and types that are actually used by other modules
Do not export internal/private constants that are only used within the same file
Before exporting a constant, verify it is referenced by other modules
Use Vitest globals (describe, it, expect) without imports in test blocks
Never use await import() dynamic imports anywhere in the codebase
Never use dynamic imports inside Vitest test blocks
Use fs-fixture createFixture() for mock Claude data directories in tests
All tests must use current Claude 4 models (not Claude 3)
Test coverage should include both Sonnet and Opus models
Model names in tests must exactly match LiteLLM pricing database entries
Use logger.ts instead of console.log for logging

Files:

  • apps/ccusage/src/commands/hourly.ts
  • apps/ccusage/src/data-loader.ts
**/data-loader.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Silently skip malformed JSONL lines during parsing in data-loader.ts

Files:

  • apps/ccusage/src/data-loader.ts
🧠 Learnings (8)
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/**/*.ts : Use `.ts` extensions for local imports (e.g., `import { foo } from './utils.ts'`)

Applied to files:

  • apps/ccusage/src/commands/hourly.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Use `fs-fixture` with `createFixture()` to simulate Claude data in tests

Applied to files:

  • apps/ccusage/src/data-loader.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : In tests, use current Claude 4 models (sonnet-4, opus-4)

Applied to files:

  • apps/ccusage/src/data-loader.ts
📚 Learning: 2025-09-18T16:07:16.293Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/codex/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:07:16.293Z
Learning: Tests should use fs-fixture with using to ensure cleanup

Applied to files:

  • apps/ccusage/src/data-loader.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Use Vitest globals (`describe`, `it`, `expect`) without imports in test blocks

Applied to files:

  • apps/ccusage/src/data-loader.ts
📚 Learning: 2025-09-18T17:43:09.255Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-18T17:43:09.255Z
Learning: Applies to **/*.ts : Use fs-fixture createFixture() for mock Claude data directories in tests

Applied to files:

  • apps/ccusage/src/data-loader.ts
📚 Learning: 2025-09-17T18:29:15.764Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/mcp/CLAUDE.md:0-0
Timestamp: 2025-09-17T18:29:15.764Z
Learning: Applies to apps/mcp/**/*.{test,spec}.ts : Use `fs-fixture` for mock data in tests of MCP server functionality

Applied to files:

  • apps/ccusage/src/data-loader.ts
📚 Learning: 2025-09-17T18:29:15.764Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/mcp/CLAUDE.md:0-0
Timestamp: 2025-09-17T18:29:15.764Z
Learning: Applies to apps/mcp/**/*.{test,spec}.ts : Vitest globals enabled: use `describe`, `it`, `expect` directly without importing them

Applied to files:

  • apps/ccusage/src/data-loader.ts
🧬 Code graph analysis (2)
apps/ccusage/src/commands/hourly.ts (8)
apps/ccusage/src/_shared-args.ts (1)
  • sharedCommandConfig (116-119)
apps/ccusage/src/_config-loader-tokens.ts (2)
  • loadConfig (146-197)
  • mergeConfigWithArgs (212-298)
apps/ccusage/src/data-loader.ts (1)
  • loadHourlyUsageData (1100-1265)
apps/ccusage/src/debug.ts (2)
  • detectMismatches (75-218)
  • printMismatchReport (225-309)
apps/ccusage/src/calculate-cost.ts (1)
  • createTotalsObject (77-82)
apps/ccusage/src/_jq-processor.ts (1)
  • processWithJq (10-36)
packages/terminal/src/table.ts (6)
  • UsageReportConfig (415-424)
  • createUsageReportTable (443-500)
  • addEmptySeparatorRow (566-569)
  • formatUsageDataRow (509-532)
  • pushBreakdownRows (369-410)
  • formatTotalsRow (540-559)
apps/ccusage/src/_project-names.ts (1)
  • formatProjectName (137-152)
apps/ccusage/src/data-loader.ts (6)
apps/codex/src/data-loader.ts (1)
  • LoadOptions (169-171)
apps/ccusage/src/_types.ts (2)
  • ModelName (91-91)
  • createISOTimestamp (113-113)
apps/ccusage/src/_pricing-fetcher.ts (1)
  • PricingFetcher (16-25)
apps/ccusage/src/_consts.ts (1)
  • DEFAULT_LOCALE (148-148)
apps/ccusage/src/calculate-cost.ts (1)
  • calculateTotals (48-67)
apps/ccusage/src/_date-utils.ts (1)
  • sortByDate (94-108)
🔇 Additional comments (2)
apps/ccusage/src/data-loader.ts (2)

1263-1264: LGTM! The --order flag is properly respected.

The sorting correctly honors options?.order and only defaults to 'asc' when not specified, which aligns with the expected chronological behavior for hourly data.


2950-3036: Excellent alignment test for data integrity.

This test verifies the critical property documented in the JSDoc: hourly totals sum to daily totals for the same timezone-aware day. It loads both hourly and daily data, aggregates each independently, and asserts equality across all token types and costs. This provides strong confidence in the correctness of both loaders.

Comment on lines +129 to +133
// Show debug information if requested
if (mergedOptions.debug && !useJson) {
const mismatchStats = await detectMismatches(undefined);
printMismatchReport(mismatchStats, mergedOptions.debugSamples as number | undefined);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Pass custom claudePath to debug mismatch detection.

When detectMismatches is called with undefined, it uses the default Claude path. If the user specified a custom claudePath via config or --config, the debug report will analyze different data than what's displayed in the hourly report.

Apply this diff to pass the custom path:

 if (mergedOptions.debug && !useJson) {
-  const mismatchStats = await detectMismatches(undefined);
+  const mismatchStats = await detectMismatches(mergedOptions.claudePath);
   printMismatchReport(mismatchStats, mergedOptions.debugSamples as number | undefined);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Show debug information if requested
if (mergedOptions.debug && !useJson) {
const mismatchStats = await detectMismatches(undefined);
printMismatchReport(mismatchStats, mergedOptions.debugSamples as number | undefined);
}
// Show debug information if requested
if (mergedOptions.debug && !useJson) {
const mismatchStats = await detectMismatches(mergedOptions.claudePath);
printMismatchReport(mismatchStats, mergedOptions.debugSamples as number | undefined);
}
🤖 Prompt for AI Agents
In apps/ccusage/src/commands/hourly.ts around lines 129 to 133, the debug path
for mismatch detection is hardcoded by calling detectMismatches(undefined) which
ignores a user-provided claudePath; change the call to pass the
configured/custom path (e.g., detectMismatches(mergedOptions.claudePath)) so the
debug mismatch report uses the same Claude instance as the hourly run, leaving
the printMismatchReport call and debugSamples handling unchanged.

Comment thread apps/ccusage/src/commands/hourly.ts
- Change forceCompact from ctx.values.compact to mergedOptions.compact
- Ensures config file settings are properly merged with CLI flags
- Fixes inconsistency with other option handling in hourly command

Note: CodeRabbit also suggested passing claudePath to detectMismatches,
but this option is not available in mergedOptions for any command.
All commands (daily, monthly, weekly, session) use undefined.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
apps/ccusage/src/commands/hourly.ts (1)

129-133: Pass custom claudePath to debug mismatch detection.

When detectMismatches is called with undefined, it uses the default Claude path. If the user specified a custom claudePath via config or --config, the debug report will analyze different data than what's displayed in the hourly report.

Apply this diff to pass the custom path:

 if (mergedOptions.debug && !useJson) {
-  const mismatchStats = await detectMismatches(undefined);
+  const mismatchStats = await detectMismatches(mergedOptions.claudePath);
   printMismatchReport(mismatchStats, mergedOptions.debugSamples as number | undefined);
 }
🧹 Nitpick comments (1)
apps/ccusage/src/commands/hourly.ts (1)

1-265: Consider adding in-source tests for command logic.

According to coding guidelines, tests should be written in-source using if (import.meta.vitest != null) blocks. While the data-loader has tests for loadHourlyUsageData, the command logic itself (projectAliases parsing, today filtering, totals calculation, project grouping) would benefit from test coverage.

Based on coding guidelines.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d1588f0 and ecf8a7e.

📒 Files selected for processing (1)
  • apps/ccusage/src/commands/hourly.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
apps/ccusage/src/**/*.ts

📄 CodeRabbit inference engine (apps/ccusage/CLAUDE.md)

apps/ccusage/src/**/*.ts: Write tests in-source using if (import.meta.vitest != null) blocks instead of separate test files
Use Vitest globals (describe, it, expect) without imports in test blocks
In tests, use current Claude 4 models (sonnet-4, opus-4)
Use fs-fixture with createFixture() to simulate Claude data in tests
Only export symbols that are actually used by other modules
Do not use console.log; use the logger utilities from src/logger.ts instead

Files:

  • apps/ccusage/src/commands/hourly.ts
apps/ccusage/**/*.ts

📄 CodeRabbit inference engine (apps/ccusage/CLAUDE.md)

apps/ccusage/**/*.ts: NEVER use await import() dynamic imports anywhere (especially in tests)
Prefer @praha/byethrow Result type for error handling instead of try-catch
Use .ts extensions for local imports (e.g., import { foo } from './utils.ts')

Files:

  • apps/ccusage/src/commands/hourly.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Use tab indentation and double quotes (ESLint formatting)
Do not use console.log; only allow where explicitly disabled via eslint-disable
Always use Node.js path utilities for file paths for cross-platform compatibility
Use .ts extensions for local file imports (e.g., import { foo } from './utils.ts')
Prefer @praha/byethrow Result type over traditional try-catch for functional error handling
Use Result.try() to wrap operations that may throw (e.g., JSON parsing)
Use Result.isFailure() for checking errors instead of negating isSuccess()
Use early return on failures (e.g., if (Result.isFailure(r)) continue) instead of ternary patterns
For async operations, create a wrapper using Result.try() and call it
Keep traditional try-catch only for complex file I/O or legacy code that’s hard to refactor
Always use Result.isFailure() and Result.isSuccess() type guards for clarity
Variables use camelCase naming
Types use PascalCase naming
Constants can use UPPER_SNAKE_CASE
Only export constants, functions, and types that are actually used by other modules
Do not export internal/private constants that are only used within the same file
Before exporting a constant, verify it is referenced by other modules
Use Vitest globals (describe, it, expect) without imports in test blocks
Never use await import() dynamic imports anywhere in the codebase
Never use dynamic imports inside Vitest test blocks
Use fs-fixture createFixture() for mock Claude data directories in tests
All tests must use current Claude 4 models (not Claude 3)
Test coverage should include both Sonnet and Opus models
Model names in tests must exactly match LiteLLM pricing database entries
Use logger.ts instead of console.log for logging

Files:

  • apps/ccusage/src/commands/hourly.ts
🧠 Learnings (3)
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/**/*.ts : Use `.ts` extensions for local imports (e.g., `import { foo } from './utils.ts'`)

Applied to files:

  • apps/ccusage/src/commands/hourly.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : Use `fs-fixture` with `createFixture()` to simulate Claude data in tests

Applied to files:

  • apps/ccusage/src/commands/hourly.ts
📚 Learning: 2025-09-18T16:06:37.474Z
Learnt from: CR
Repo: ryoppippi/ccusage PR: 0
File: apps/ccusage/CLAUDE.md:0-0
Timestamp: 2025-09-18T16:06:37.474Z
Learning: Applies to apps/ccusage/src/**/*.ts : In tests, use current Claude 4 models (sonnet-4, opus-4)

Applied to files:

  • apps/ccusage/src/commands/hourly.ts
🧬 Code graph analysis (1)
apps/ccusage/src/commands/hourly.ts (7)
apps/ccusage/src/_config-loader-tokens.ts (2)
  • loadConfig (146-197)
  • mergeConfigWithArgs (212-298)
apps/ccusage/src/data-loader.ts (1)
  • loadHourlyUsageData (1100-1265)
apps/ccusage/src/debug.ts (2)
  • detectMismatches (75-218)
  • printMismatchReport (225-309)
apps/ccusage/src/calculate-cost.ts (1)
  • createTotalsObject (77-82)
apps/ccusage/src/_jq-processor.ts (1)
  • processWithJq (10-36)
packages/terminal/src/table.ts (6)
  • UsageReportConfig (415-424)
  • createUsageReportTable (443-500)
  • addEmptySeparatorRow (566-569)
  • formatUsageDataRow (509-532)
  • pushBreakdownRows (369-410)
  • formatTotalsRow (540-559)
apps/ccusage/src/_project-names.ts (1)
  • formatProjectName (137-152)

@ryoppippi ryoppippi closed this Nov 18, 2025
@ryoppippi

Copy link
Copy Markdown
Member

we can add those commands infinitely

@humanwritten

Copy link
Copy Markdown
Author

sure thing no worries, I tried to follow your practices as best as I could, will just keep it as a fork for myself for what I need. thanks!

@humanwritten humanwritten deleted the feat/hourly-usage-report-fixed branch November 18, 2025 21:26
@ryoppippi

Copy link
Copy Markdown
Member

Thanks

@ryoppippi

Copy link
Copy Markdown
Member

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants