Skip to content

Conversation

@a-c-m
Copy link
Contributor

@a-c-m a-c-m commented Jun 16, 2025

Summary

This PR implements a new blocks command to analyze Claude Code usage in 5-hour billing windows as requested in #23.

Features

  • 5-Hour Block Tracking: Groups usage into Claude's 5-hour session windows that start with first message
  • Active Block Monitoring: Shows time elapsed/remaining and burn rate for current sessions
  • Token Limit Support: Set limits with -t <number> or use -t max to automatically use highest previous block
  • Usage Projections: Projects total usage if current burn rate continues for the full 5 hours
  • Recent Filter: --recent flag shows only last 3 days of blocks
  • Detailed Active View: --active flag shows detailed view of current active block

Implementation Details

  • Core logic in src/utils/five-hour-blocks.ts handles block detection and calculations
  • New CLI command in src/commands/blocks.ts with table and JSON output formats
  • Integrated with existing data loader infrastructure
  • Added comprehensive documentation to README

Example Output

┌────────────────────────────────────────────────────────┬─────────────┬─────────────────────────┬────────┬────────┬─────────┐
│ Block Start                                            │ Duration/   │ Models                  │ Tokens │ %      │ Cost    │
│                                                        │ Status      │                         │        │        │         │
├────────────────────────────────────────────────────────┼─────────────┼─────────────────────────┼────────┼────────┼─────────┤
│ 6/16/2025, 6:21:16 PM (1h 25m elapsed, 3h 35m remain) │ ACTIVE      │ claude-opus-4-20250514  │ 25,979 │ 41.8%  │ $74.54  │
├────────────────────────────────────────────────────────┼─────────────┼─────────────────────────┼────────┼────────┼─────────┤
│                      (assuming 62,081 token limit)    │ REMAINING   │                         │ 36,102 │ 58.2%  │         │
├────────────────────────────────────────────────────────┼─────────────┼─────────────────────────┼────────┼────────┼─────────┤
│                       (assuming current burn rate)    │ PROJECTED   │                         │ 91,443 │ 147.3% │ $262.09 │
└────────────────────────────────────────────────────────┴─────────────┴─────────────────────────┴────────┴────────┴─────────┘

Testing

  • All existing tests pass
  • TypeScript compilation successful
  • Manual testing with various scenarios including active blocks, token limits, and edge cases

Fixes #23

🤖 Generated with Claude Code

Co-Authored-By: @a-c-m

Summary by CodeRabbit

  • New Features
    • Introduced a new "blocks" command to the CLI, providing usage reports grouped by 5-hour billing windows.
    • Added detailed reporting for active and recent 5-hour blocks, including projections, token limits, and cost calculations.
    • Enhanced output options with JSON formatting and customizable display modes.
  • Documentation
    • Updated the README with a comprehensive section on the new 5-hour blocks report, including usage examples and feature descriptions.

Implements a new `blocks` command to analyze Claude Code usage in 5-hour billing windows as requested in ryoppippi#23:

- Track active blocks with time remaining and burn rate projections
- Support token limits with `-t` parameter (including `-t max` for highest previous block)
- Add `--recent` flag to show last 3 days of blocks
- Add `--active` flag for detailed view of current active block
- Include REMAINING and PROJECTED rows in table view with percentage tracking
- Fix max token calculation to include all blocks (not filtering synthetic models)
- Update README with blocks command documentation

The blocks report helps users understand:
- When their 5-hour session windows start and end
- How many tokens they've used vs their limit
- Projected usage if current burn rate continues
- Time remaining in active sessions

Fixes ryoppippi#23

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

Co-Authored-By: Claude <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Jun 16, 2025

Warning

Rate limit exceeded

@ryoppippi has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 0 minutes and 20 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between bf87e0b and 4c0429b.

📒 Files selected for processing (5)
  • src/commands/blocks.ts (1 hunks)
  • src/data-loader.test.ts (2 hunks)
  • src/data-loader.ts (2 hunks)
  • src/five-hour-blocks.internal.test.ts (1 hunks)
  • src/five-hour-blocks.internal.ts (1 hunks)

Walkthrough

A new CLI feature and supporting utilities were added to track and report Claude usage in 5-hour billing blocks. This includes a new blocks command, data loading and grouping logic for 5-hour sessions, calculation of burn rates and projections, and documentation updates describing usage and options.

Changes

File(s) Change Summary
README.md Added documentation for the new "5-Hour Blocks Report" and ccusage blocks usage examples.
src/commands/blocks.ts Introduced the blocks CLI command for reporting usage grouped by 5-hour billing blocks.
src/commands/index.ts Registered the new blocks command in the CLI subcommands map.
src/data-loader.ts Added loadFiveHourBlockData to load and group usage data into 5-hour blocks.
src/utils/five-hour-blocks.ts New module: utilities for segmenting, analyzing, and projecting 5-hour usage blocks.
src/utils/table.ts Extended TableRow type and related methods to support richer cell formatting with alignment metadata.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI (blocksCommand)
    participant DataLoader
    participant FiveHourUtils

    User->>CLI (blocksCommand): Run `ccusage blocks` with options
    CLI (blocksCommand)->>DataLoader: loadFiveHourBlockData(options)
    DataLoader->>FiveHourUtils: identifyFiveHourBlocks(entries)
    FiveHourUtils-->>DataLoader: List of FiveHourBlock
    DataLoader-->>CLI (blocksCommand): FiveHourBlock[]
    CLI (blocksCommand)->>CLI (blocksCommand): Filter, calculate burn rate, projections
    CLI (blocksCommand)->>User: Output report (table or JSON)
Loading

Assessment against linked issues

Objective Addressed Explanation
Add ability to track 5-hour sessions/blocks and report how many have been used for month (#23)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes found.

Poem

In five-hour hops, the bunnies leap,
Tracking Claude’s usage, never asleep.
Blocks of time, now clear as day,
Burn rates and tokens on display.
With tables and charts, the story unfolds—
Billing made simple, as the future beholds!
🕔🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @a-c-m, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the ccusage CLI tool by adding a dedicated command to monitor and report on Claude Code usage within its unique 5-hour billing windows. It provides users with critical information on current and projected token consumption and costs, helping them manage their usage more effectively and avoid unexpected overages. The changes involve new command implementation, core data processing logic, and updated documentation.

Highlights

  • New Command: 5-Hour Blocks Report: Introduced a new blocks command to analyze Claude Code usage, grouping it into 5-hour billing windows. This addresses the request in #23.
  • Usage Tracking and Projections: The new command provides detailed insights into 5-hour blocks, including active block monitoring with time elapsed/remaining, burn rate calculations, and usage projections if the current burn rate continues.
  • Token Limit Management: Users can now set token limits (-t <number>) or automatically use the highest previous block's token count (-t max) to track if they are approaching or exceeding their limits within a session.
  • Flexible Reporting Options: The blocks command supports various filtering options like --recent (last 3 days) and --active (detailed view of current block), and offers both human-readable table and machine-readable JSON output formats.
  • Core Logic and Data Loading: New core logic for identifying and calculating 5-hour blocks has been implemented in src/utils/five-hour-blocks.ts, and the data loading infrastructure in src/data-loader.ts has been extended to support this new reporting mechanism.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new blocks command for analyzing Claude Code usage within 5-hour billing windows. The implementation includes logic for identifying these blocks and calculating active block metrics. I've identified a couple of areas for minor improvement related to code maintainability and error reporting.

Comment on lines 159 to 169
let limit: number | undefined;
if (ctx.values.tokenLimit === 'max') {
// Use the max calculated from ALL blocks before filtering
limit = maxTokensFromAll > 0 ? maxTokensFromAll : undefined;
}
else {
limit = Number.parseInt(ctx.values.tokenLimit, 10);
if (isNaN(limit)) {
limit = undefined;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The logic to parse the tokenLimit value is repeated in multiple places. Consider extracting this logic into a helper function to improve maintainability.

Comment on lines 886 to 888
catch {
// Skip invalid JSON lines
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The catch block for JSON parsing errors is currently empty. Consider adding a debug log message to indicate that an invalid line was encountered and skipped to aid in debugging.

			}
			catch (error) {
				// Skip invalid JSON lines but log for debugging purposes
        logger.debug(`Skipping invalid JSON line: ${(error instanceof Error ? error.message : String(error))}`);
			}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (9)
src/utils/five-hour-blocks.ts (2)

185-188: Avoid early rounding – it degrades projection accuracy

calculateBurnRate rounds both tokensPerMinute and costPerHour, then projectBlockUsage multiplies these rounded numbers to estimate totals. On long remaining windows this can introduce noticeable drift.

Return full-precision numbers from calculateBurnRate and only round when formatting for display:

-  tokensPerMinute: Math.round(tokensPerMinute),
-  costPerHour: Math.round(costPerHour * 100) / 100,
+  tokensPerMinute,
+  costPerHour,

Then apply rounding in the CLI where the values are rendered.

Also applies to: 202-207


176-180: Zero-minute blocks produce null; consider minimum window guard

For blocks where the first and last entry share the same minute, durationMinutes becomes 0, so burn-rate is null.
If that’s unintended, clamp to at least one minute to avoid silent loss of data:

const durationMinutes = Math.max(1,
  (lastEntry.getTime() - firstEntry.getTime()) / 60000);
src/data-loader.ts (2)

843-849: Stream JSONL files instead of loading them wholesale

readFile(..., 'utf-8') followed by .split('\n') loads the entire file into memory.
Large project histories can easily reach hundreds of MB, causing high RAM spikes.

Consider using an async line iterator (createInterface from readline/promises or Bun/Deno equivalents) so lines are processed incrementally:

const rl = createInterface({ input: createReadStream(file) });
for await (const line of rl) { /* parse */ }

869-872: Cost calculation throttles I/O

await calculateCostForEntry is executed inside the tight inner loop, forcing strictly sequential processing. If PricingFetcher involves I/O (e.g. remote price fetch), total load time grows linearly with message count.

Batch-fetch prices for distinct models once, or make calculateCostForEntry synchronous by pre-seeding a local pricing table.

src/commands/blocks.ts (2)

230-260: Factor out token-limit parsing to keep CLI logic DRY

parseInt/max handling is repeated in two separate branches. A tiny helper improves readability and prevents divergent behaviour on future edits.

function resolveTokenLimit(raw: string | undefined, maxFromHistory: number): number | undefined {
  if (!raw) return undefined;
  if (raw === 'max') return maxFromHistory || undefined;
  const n = Number.parseInt(raw, 10);
  return Number.isFinite(n) ? n : undefined;
}

Then call const limit = resolveTokenLimit(ctx.values.tokenLimit, maxTokensFromAll);

Also applies to: 269-279


282-299: Header alignment relies on parallel arrays – fragile for future edits

tableHeaders and tableAligns must stay in sync manually. A small slip adds a hard-to-notice column mismatch.

Wrap these in a single array of objects { title, align } to keep structure cohesive and avoid off-by-one errors.

README.md (3)

219-223: Align section title and description with established report naming.
The heading should follow the singular "Report" convention (e.g., "5-Hour Block Report") and drop the unnecessary possessive "Claude's" in the description for consistency with other sections.


224-233: Avoid contractions in code example comments.
Replace "you'll" with "you will" in the "# Set a token limit to see if you'll exceed it" line to maintain a professional, consistent tone across examples.


255-260: Use consistent terminology for report items.
The bullet list mixes "sessions" and "blocks" (e.g., "highest previous session")—it should consistently refer to "blocks" to avoid confusion with the Session report.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca257b1 and b7d4cfc.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • README.md (1 hunks)
  • src/commands/blocks.ts (1 hunks)
  • src/commands/index.ts (2 hunks)
  • src/data-loader.ts (2 hunks)
  • src/utils/five-hour-blocks.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/commands/index.ts (1)
src/commands/blocks.ts (1)
  • blocksCommand (53-400)
🔇 Additional comments (2)
src/commands/index.ts (1)

4-16: Blocks command successfully registered

The new sub-command is wired into the CLI map; no concerns here.

README.md (1)

242-253: Examples for JSON output, mode, and order are clear.
The usage examples for --json, --mode, and --order match the style of other report sections and accurately convey the options.

- Extract repeated token limit parsing logic into parseTokenLimit helper function
- Add debug logging for skipped invalid JSON lines in data loader
- Update FiveHourBlock type documentation to clarify gap block endTime semantics
- Add blocks-specific options documentation to README

Addresses feedback from code review bots on PR ryoppippi#23
Copy link

@coderabbitai coderabbitai bot left a comment

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 (2)
README.md (1)

262-265: -t/--token-limit is still missing from the global “Options” table.

You added the flag under “Blocks-specific options”, but the earlier “Options” list (lines 271-279) is intended to be the canonical global reference. Please graft the entry there as well for discoverability.

src/commands/blocks.ts (1)

53-60: Factor out parseTokenLimit to a shared util to avoid repetition.

This helper now lives in yet another file (it’s also present in other commands per earlier review). Extract it to src/utils/cli.ts (or similar) and import it here to keep a single source of truth and cut maintenance overhead.

🧹 Nitpick comments (4)
README.md (2)

219-253: Add a “5-Hour Blocks Report” bullet to the global Features list.

A new high-level capability is being introduced, but the “Features” section (lines 40-52) still omits it. A quick bullet such as
- ⏱ **5-Hour Blocks Report**: View usage grouped by 5-hour billing windows
keeps the feature matrix in sync with the rest of the docs.


262-266: Remove trailing colon in heading to satisfy MD026.

#### Blocks-specific options:#### Blocks-specific options
This silences the markdown-lint warning flagged by CI and avoids inconsistent heading style.

src/commands/blocks.ts (2)

130-141: --active silently chooses the first active block when >1 exist.

blocks.filter(block => block.isActive) can legally return several items if the grouping logic ever mis-detects overlaps (clock skew, corrupted data, tests). When that happens, the detailed view later only shows blocks[0], dropping the rest without warning.

Consider:

-if (ctx.values.active && blocks.length === 1) {
+if (ctx.values.active && blocks.length === 1) {-} else {
+} else if (ctx.values.active) {
+    logger.warn(`${blocks.length} active blocks detected; showing table view.`);
 }

Alternatively, fail fast with an explicit error.


228-246: Warn on un-parsable -t/--token-limit values.

If the user passes an invalid string (e.g. -t foo), parseTokenLimit returns undefined, but execution continues as if the flag were never supplied. Surfacing a warning avoids silent mis-configuration:

 const limit = parseTokenLimit(ctx.values.tokenLimit, maxTokensFromAll);
 if (limit) {
     …
 } else if (ctx.values.tokenLimit) {
-    /* silent */
+    logger.warn(`Ignoring invalid --token-limit value: "${ctx.values.tokenLimit}". Expected number or "max".`);
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7d4cfc and f2b808e.

📒 Files selected for processing (4)
  • README.md (1 hunks)
  • src/commands/blocks.ts (1 hunks)
  • src/data-loader.ts (2 hunks)
  • src/utils/five-hour-blocks.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/data-loader.ts
  • src/utils/five-hour-blocks.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/commands/blocks.ts (5)
src/utils/five-hour-blocks.ts (4)
  • FiveHourBlock (23-34)
  • filterRecentBlocks (215-223)
  • calculateBurnRate (168-189)
  • projectBlockUsage (191-213)
src/shared-args.internal.ts (1)
  • sharedCommandConfig (68-71)
src/logger.ts (2)
  • logger (5-5)
  • log (8-8)
src/data-loader.ts (2)
  • loadFiveHourBlockData (814-912)
  • getDefaultClaudePath (28-38)
src/utils.internal.ts (2)
  • formatNumber (3-5)
  • formatCurrency (7-9)
🪛 markdownlint-cli2 (0.17.2)
README.md

262-262: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)

tokenCounts.cacheCreationInputTokens += entry.usage.cacheCreationInputTokens;
tokenCounts.cacheReadInputTokens += entry.usage.cacheReadInputTokens;
costUSD += entry.costUSD || 0;
modelsSet.add(entry.model);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
modelsSet.add(entry.model);
// Skip synthetic model
if (entry.model !== '<synthetic>') {
modelsSet.add(entry.model);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Synthetic is actually useful to see, as it tends to be used when you hit your limit (i think)

Copy link
Contributor

Choose a reason for hiding this comment

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

@a-c-m I don't really have strong opinions about it.

I suggested it for consistency with how it's filtered out:

  • ccusage/src/data-loader.ts

    Lines 141 to 144 in 486b74d

    // Skip synthetic model
    if (modelName === '<synthetic>') {
    continue;
    }
  • ccusage/src/data-loader.ts

    Lines 179 to 182 in 486b74d

    // Skip synthetic model
    if (breakdown.modelName === '<synthetic>') {
    continue;
    }
  • ccusage/src/data-loader.ts

    Lines 295 to 303 in 486b74d

    /**
    * Extracts unique models from entries, excluding synthetic model
    */
    function extractUniqueModels<T>(
    entries: T[],
    getModel: (entry: T) => string | undefined,
    ): string[] {
    return [...new Set(entries.map(getModel).filter((m): m is string => m != null && m !== '<synthetic>'))];
    }
  • ccusage/src/data-loader.ts

    Lines 779 to 782 in 486b74d

    // Skip synthetic model
    if (model !== '<synthetic>') {
    modelsSet.add(model);
    }

Copy link
Owner

Choose a reason for hiding this comment

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

@a-c-m could you tell me why you don't skip '<synthetic>'

@dashed
Copy link
Contributor

dashed commented Jun 17, 2025

@a-c-m this works great! thanks!

I left some minor feedback about being able to see all the models used within the 5 hour sessions blocks.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/commands/blocks.ts (1)

53-60: Token-limit parsing is duplicated across commands

This helper already exists in multiple files (see earlier review). Extract it to a shared util (e.g. src/utils/cli.ts) to avoid divergence and mismatched validations.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f2b808e and ade0fef.

📒 Files selected for processing (1)
  • src/commands/blocks.ts (1 hunks)
🔇 Additional comments (2)
src/commands/blocks.ts (2)

255-307: Percentage calculations assume a valid actualTokenLimit

All % displays (percentage, remainingPercent, etc.) should skip or visibly mark the field when actualTokenLimit is undefined. Current guards prevent most cases, but edge-paths still exist (e.g., user passes -t foo). Consider early-returning with a warning when parseTokenLimit fails, to avoid partial/incorrect tables.


167-178: Unchecked division by limit when it is undefined or 0

Even with the guard above, limit is re-evaluated inline and could still be undefined if parsing fails, causing projection.totalTokens / limit to evaluate to Infinity or NaN. Safeguard before performing maths:

-const limit = parseTokenLimit(ctx.values.tokenLimit, maxTokensFromAll);
-return limit
-  ? {
-      ...
-      percentUsed: (projection.totalTokens / limit) * 100,
-      ...
-    }
-  : undefined;
+const limit = parseTokenLimit(ctx.values.tokenLimit, maxTokensFromAll);
+if (!limit) return undefined;
+return {
+  ...
+  percentUsed: (projection.totalTokens / limit) * 100,
+  ...
+};

Likely an incorrect or invalid review comment.

@ryoppippi
Copy link
Owner

OK I start reviewing!!

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jun 17, 2025

Open in StackBlitz

npm i https://pkg.pr.new/ryoppippi/ccusage@80

commit: 4c0429b

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/commands/blocks.ts (1)

57-66: parseTokenLimit still accepts 0 / negative numbers — divide-by-zero risk

A prior review already flagged this, but the implementation was not updated.
0 (or <0) propagates to % calculations and JSON projection (projection.totalTokens / limit) causing Infinity, NaN, or runtime ZeroDivisionError equivalents.

- const limit = Number.parseInt(value, 10);
- return Number.isNaN(limit) ? undefined : limit;
+ const limit = Number.parseInt(value, 10);
+ if (Number.isNaN(limit) || limit <= 0) {
+   return undefined; // invalid
+ }
+ return limit;

Please also add a unit test covering "0", "-1", and "max" paths.

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

897-911: Reuse filterByDateRange() instead of open-coded string compare

The bespoke date check duplicates the earlier filterByDateRange helper but is slightly different (empty-string handling, replace(/-/g,'') etc.). Divergent logic risks subtle regressions.

- const filtered = (options?.since ... )
-   ? blocks.filter(/* manual compare */)
-   : blocks;
+ const filtered = filterByDateRange(
+   blocks,
+   b => b.startTime.toISOString(),
+   options?.since,
+   options?.until,
+ );

This keeps behaviour consistent across all loaders and removes ~15 lines.

src/commands/blocks.ts (1)

68-90: Extract shared CLI arg parsing (DRY)

active, recent, and especially tokenLimit flags plus parseTokenLimit() logic have been duplicated once already (see previous review). Move them into sharedCommandConfig or a tiny blocks-args.ts to avoid repeating validation & help-text in future commands.

README.md (1)

262-267: Remove trailing colon in heading to satisfy markdownlint (MD026)

Current:

#### Blocks-specific options:

Recommended:

#### Blocks-specific options

Very minor, but keeps CI/preview clean.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ade0fef and a9346ef.

📒 Files selected for processing (4)
  • README.md (1 hunks)
  • src/commands/blocks.ts (1 hunks)
  • src/data-loader.ts (2 hunks)
  • src/utils/five-hour-blocks.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils/five-hour-blocks.ts
🧰 Additional context used
🪛 LanguageTool
README.md

[uncategorized] ~264-~264: Loose punctuation mark.
Context: ...ons: - -t, --token-limit <number|max>: Set token limit for quota warnings (use...

(UNLIKELY_OPENING_PUNCTUATION)

🪛 markdownlint-cli2 (0.17.2)
README.md

262-262: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)

🪛 ESLint
src/commands/blocks.ts

[error] 68-381: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 68-68: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 71-90: Unsafe assignment of an any value.

(ts/no-unsafe-assignment)


[error] 93-93: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 93-93: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 94-94: Unsafe member access .level on an error typed value.

(ts/no-unsafe-member-access)


[error] 98-98: Unsafe assignment of an any value.

(ts/no-unsafe-assignment)


[error] 98-98: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 99-99: Unsafe assignment of an any value.

(ts/no-unsafe-assignment)


[error] 99-99: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 101-101: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 102-102: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 106-106: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 106-106: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 107-107: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 110-110: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 110-110: Unsafe member access .warn on an error typed value.

(ts/no-unsafe-member-access)


[error] 112-112: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 112-112: Unsafe member access .exit on an error typed value.

(ts/no-unsafe-member-access)


[error] 117-117: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 126-126: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 126-126: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 127-127: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 127-127: Unsafe member access .info on an error typed value.

(ts/no-unsafe-member-access)


[error] 132-132: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 132-132: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 136-136: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 136-136: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 139-139: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 139-139: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 140-140: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 143-143: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 143-143: Unsafe member access .info on an error typed value.

(ts/no-unsafe-member-access)


[error] 145-145: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 145-145: Unsafe member access .exit on an error typed value.

(ts/no-unsafe-member-access)


[error] 149-149: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 149-149: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 172-172: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 174-174: Unsafe argument of type any assigned to a parameter of type string | undefined.

(ts/no-unsafe-argument)


[error] 174-174: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 191-191: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 195-195: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 195-195: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 199-199: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 199-199: Unsafe member access .warn on an error typed value.

(ts/no-unsafe-member-access)


[error] 200-200: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 200-200: Unsafe member access .exit on an error typed value.

(ts/no-unsafe-member-access)


[error] 205-205: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 205-205: Unsafe member access .box on an error typed value.

(ts/no-unsafe-member-access)


[error] 215-215: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 215-215: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 215-215: Unsafe member access .cyan on an error typed value.

(ts/no-unsafe-member-access)


[error] 215-215: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 215-215: Unsafe member access .yellow on an error typed value.

(ts/no-unsafe-member-access)


[error] 216-216: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 216-216: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 216-216: Unsafe member access .green on an error typed value.

(ts/no-unsafe-member-access)


[error] 218-218: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 218-218: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 218-218: Unsafe member access .bold on an error typed value.

(ts/no-unsafe-member-access)


[error] 219-219: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 220-220: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 221-221: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 224-224: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 224-224: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 224-224: Unsafe member access .bold on an error typed value.

(ts/no-unsafe-member-access)


[error] 225-225: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 226-226: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 230-230: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 230-230: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 230-230: Unsafe member access .bold on an error typed value.

(ts/no-unsafe-member-access)


[error] 231-231: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 232-232: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 234-234: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 236-236: Unsafe argument of type any assigned to a parameter of type string | undefined.

(ts/no-unsafe-argument)


[error] 236-236: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 241-245: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 242-242: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 242-242: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 244-244: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 244-244: Unsafe member access .yellow on an error typed value.

(ts/no-unsafe-member-access)


[error] 245-245: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 245-245: Unsafe member access .green on an error typed value.

(ts/no-unsafe-member-access)


[error] 247-247: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 247-247: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 247-247: Unsafe member access .bold on an error typed value.

(ts/no-unsafe-member-access)


[error] 248-248: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 249-249: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 250-250: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 251-251: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 258-258: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 258-258: Unsafe member access .box on an error typed value.

(ts/no-unsafe-member-access)


[error] 261-261: Unsafe argument of type any assigned to a parameter of type string | undefined.

(ts/no-unsafe-argument)


[error] 261-261: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 275-279: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 275-279: Unsafe construction of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 285-285: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 285-285: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 286-286: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 286-286: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 287-287: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 287-287: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 288-288: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 288-288: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 291-291: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 291-291: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 293-293: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 293-293: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 294-294: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 294-294: Unsafe member access .push on an error typed value.

(ts/no-unsafe-member-access)


[error] 299-299: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 299-299: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 299-299: Unsafe member access .green on an error typed value.

(ts/no-unsafe-member-access)


[error] 312-312: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 312-312: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 316-316: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 316-316: Unsafe member access .push on an error typed value.

(ts/no-unsafe-member-access)


[error] 324-326: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 326-326: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 326-326: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 330-332: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 332-332: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 332-332: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 335-335: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 335-335: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 335-335: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 336-336: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 336-336: Unsafe member access .blue on an error typed value.

(ts/no-unsafe-member-access)


[error] 342-342: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 342-342: Unsafe member access .push on an error typed value.

(ts/no-unsafe-member-access)


[error] 349-351: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 350-350: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 350-350: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 354-354: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 354-354: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 354-354: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 355-355: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 355-355: Unsafe member access .yellow on an error typed value.

(ts/no-unsafe-member-access)


[error] 364-364: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 364-364: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 371-371: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 371-371: Unsafe member access .push on an error typed value.

(ts/no-unsafe-member-access)


[error] 377-377: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 377-377: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 377-377: Unsafe member access .toString on an error typed value.

(ts/no-unsafe-member-access)

src/data-loader.ts

[error] 818-818: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 818-818: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 818-818: Unsafe member access .join on an error typed value.

(ts/no-unsafe-member-access)


[error] 819-822: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 819-819: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 820-820: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 824-824: Unsafe member access .length on an error typed value.

(ts/no-unsafe-member-access)


[error] 829-829: Unsafe argument of type error typed assigned to a parameter of type string[].

(ts/no-unsafe-argument)


[error] 832-832: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 844-844: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 844-844: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 845-848: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 845-848: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 845-847: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 845-846: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 846-846: Unsafe member access .trim on an error typed value.

(ts/no-unsafe-member-access)


[error] 847-847: Unsafe member access .split on an error typed value.

(ts/no-unsafe-member-access)


[error] 848-848: Unsafe member access .filter on an error typed value.

(ts/no-unsafe-member-access)


[error] 848-848: Unsafe member access .length on an any value.

(ts/no-unsafe-member-access)


[error] 852-852: Unsafe argument of type error typed assigned to a parameter of type string.

(ts/no-unsafe-argument)


[error] 853-853: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 853-853: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 853-853: Unsafe member access .safeParse on an error typed value.

(ts/no-unsafe-member-access)


[error] 854-854: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 854-854: Unsafe member access .success on an error typed value.

(ts/no-unsafe-member-access)


[error] 857-857: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 857-857: Unsafe member access .output on an error typed value.

(ts/no-unsafe-member-access)


[error] 869-871: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 871-871: Unsafe member access .costUSD on an error typed value.

(ts/no-unsafe-member-access)


[error] 874-874: Unsafe argument of type error typed assigned to a parameter of type string | number | Date.

(ts/no-unsafe-argument)


[error] 874-874: Unsafe member access .timestamp on an error typed value.

(ts/no-unsafe-member-access)


[error] 876-876: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 876-876: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 877-877: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 877-877: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 878-878: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 878-878: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 879-879: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 879-879: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 881-881: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 882-882: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 882-882: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 883-883: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 883-883: Unsafe member access .version on an error typed value.

(ts/no-unsafe-member-access)


[error] 888-888: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 888-888: Unsafe member access .debug on an error typed value.

(ts/no-unsafe-member-access)

🔇 Additional comments (1)
src/commands/blocks.ts (1)

172-186: Guard % calculations against undefined / missing limit

Even after fixing parseTokenLimit, percentUsed is evaluated without ensuring limit is defined (it might be undefined if user omits -t). Wrap the division:

- percentUsed: (projection.totalTokens / limit) * 100,
+ percentUsed: limit != null ? (projection.totalTokens / limit) * 100 : undefined,

Prevents accidental NaN in JSON output.

Likely an incorrect or invalid review comment.

Comment on lines +814 to +886
export async function loadFiveHourBlockData(
options?: LoadOptions,
): Promise<FiveHourBlock[]> {
const claudePath = options?.claudePath ?? getDefaultClaudePath();
const claudeDir = path.join(claudePath, 'projects');
const files = await glob(['**/*.jsonl'], {
cwd: claudeDir,
absolute: true,
});

if (files.length === 0) {
return [];
}

// Sort files by timestamp to ensure chronological processing
const sortedFiles = await sortFilesByTimestamp(files);

// Fetch pricing data for cost calculation only when needed
const mode = options?.mode ?? 'auto';

// Use PricingFetcher with using statement for automatic cleanup
using fetcher = mode === 'display' ? null : new PricingFetcher();

// Track processed message+request combinations for deduplication
const processedHashes = new Set<string>();

// Collect all valid data entries first
const allEntries: LoadedUsageEntry[] = [];

for (const file of sortedFiles) {
const content = await readFile(file, 'utf-8');
const lines = content
.trim()
.split('\n')
.filter(line => line.length > 0);

for (const line of lines) {
try {
const parsed = JSON.parse(line) as unknown;
const result = v.safeParse(UsageDataSchema, parsed);
if (!result.success) {
continue;
}
const data = result.output;

// Check for duplicate message + request ID combination
const uniqueHash = createUniqueHash(data);
if (isDuplicateEntry(uniqueHash, processedHashes)) {
// Skip duplicate message
continue;
}

// Mark this combination as processed
markAsProcessed(uniqueHash, processedHashes);

const cost = fetcher != null
? await calculateCostForEntry(data, mode, fetcher)
: data.costUSD ?? 0;

allEntries.push({
timestamp: new Date(data.timestamp),
usage: {
inputTokens: data.message.usage.input_tokens,
outputTokens: data.message.usage.output_tokens,
cacheCreationInputTokens: data.message.usage.cache_creation_input_tokens ?? 0,
cacheReadInputTokens: data.message.usage.cache_read_input_tokens ?? 0,
},
costUSD: cost,
model: data.message.model ?? 'unknown',
version: data.version,
});
}
catch (error) {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Factor out duplicated JSONL-parsing pipeline into a shared helper

loadFiveHourBlockData() re-implements the exact read-→split-→safeParse-→dedupe logic that already exists in loadDailyUsageData() and loadSessionData().
Keeping three copies of ~70 lines each means every bug-fix (e.g. schema update, cost-calc tweak, dedup rule) now needs to be patched in three places.

Diff sketch for consolidation:

- // copy-pasted parsing loop ...
+ const allEntries = await parseClaudeJsonlFiles(
+   sortedFiles,
+   processedHashes,
+   mode,
+   fetcher,
+   ({ message, version, timestamp }, cost) => ({
+     timestamp: new Date(timestamp),
+     usage: { ... },
+     costUSD: cost,
+     model: message.model ?? 'unknown',
+     version,
+   }),
+ );

Benefits: single point of truth, ~200 LOC less, easier test coverage.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 ESLint

[error] 818-818: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 818-818: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 818-818: Unsafe member access .join on an error typed value.

(ts/no-unsafe-member-access)


[error] 819-822: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 819-819: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 820-820: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 824-824: Unsafe member access .length on an error typed value.

(ts/no-unsafe-member-access)


[error] 829-829: Unsafe argument of type error typed assigned to a parameter of type string[].

(ts/no-unsafe-argument)


[error] 832-832: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 844-844: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 844-844: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 845-848: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 845-848: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 845-847: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 845-846: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 846-846: Unsafe member access .trim on an error typed value.

(ts/no-unsafe-member-access)


[error] 847-847: Unsafe member access .split on an error typed value.

(ts/no-unsafe-member-access)


[error] 848-848: Unsafe member access .filter on an error typed value.

(ts/no-unsafe-member-access)


[error] 848-848: Unsafe member access .length on an any value.

(ts/no-unsafe-member-access)


[error] 852-852: Unsafe argument of type error typed assigned to a parameter of type string.

(ts/no-unsafe-argument)


[error] 853-853: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 853-853: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 853-853: Unsafe member access .safeParse on an error typed value.

(ts/no-unsafe-member-access)


[error] 854-854: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 854-854: Unsafe member access .success on an error typed value.

(ts/no-unsafe-member-access)


[error] 857-857: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 857-857: Unsafe member access .output on an error typed value.

(ts/no-unsafe-member-access)


[error] 869-871: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 871-871: Unsafe member access .costUSD on an error typed value.

(ts/no-unsafe-member-access)


[error] 874-874: Unsafe argument of type error typed assigned to a parameter of type string | number | Date.

(ts/no-unsafe-argument)


[error] 874-874: Unsafe member access .timestamp on an error typed value.

(ts/no-unsafe-member-access)


[error] 876-876: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 876-876: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 877-877: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 877-877: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 878-878: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 878-878: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 879-879: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 879-879: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 881-881: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 882-882: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 882-882: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 883-883: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 883-883: Unsafe member access .version on an error typed value.

(ts/no-unsafe-member-access)

🤖 Prompt for AI Agents
In src/data-loader.ts between lines 814 and 886, the JSONL parsing, splitting,
safe parsing, and deduplication logic in loadFiveHourBlockData is duplicated
from loadDailyUsageData and loadSessionData. To fix this, extract this common
pipeline into a shared helper function that accepts a file list and returns
parsed, deduplicated entries. Then refactor loadFiveHourBlockData and the other
functions to call this helper, reducing code duplication and centralizing logic
for easier maintenance and testing.

@ryoppippi ryoppippi requested a review from Copilot June 17, 2025 12:23
@ryoppippi
Copy link
Owner

@ben-vargas could you review it? it looks brilliant

- Adjusted the construction of the gap row to ensure consistent column
  alignment, especially when `actualTokenLimit` is present.
- Updated percentage cell in the projection row to always display the
  percentage value, using plain text instead of color coding when over
  100%.
- These changes improve table readability and maintain consistent
  formatting regardless of token limit presence.

This comment was marked as outdated.

…issues

Changed formatModels function to use comma separation instead of newlines
when displaying multiple model names in the blocks table. This prevents
table rows from being split across multiple lines, maintaining proper
column alignment and improving readability.

The change affects only the visual presentation - functionality remains
the same. Model names are now displayed as:
"claude-opus-4-20250514, <synthetic>, claude-sonnet-4-20250514"
instead of being split across separate lines.
- Replace array index access with .at(-1) method for safer null handling
- Remove unreachable null check that was flagged by code review
- Use proper null check with .at() method instead of non-null assertion
- Maintains same behavior while being more explicit about potential edge cases
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/data-loader.ts (1)

852-899: Third copy-paste of the JSONL parsing pipeline – please extract a shared helper

The block between lines 852-899 is functionally identical to the loops inside loadDailyUsageData() and loadSessionData().
Past reviews already flagged this duplication (see earlier comment #823-895). Centralising the logic would:

  • remove ~200 LOC of repetition
  • ensure schema / dedup / cost fixes land in one place
  • simplify testing.

Consider a helper like parseClaudeJsonlFiles(files, processedHashes, mode, fetcher): Promise<LoadedUsageEntry[]>.

src/commands/blocks.ts (1)

58-67: parseTokenLimit() accepts 0/negative limits – causes divide-by-zero & bogus % calculations

Returning 0 (or a negative number) is treated later as a valid limit, producing Infinity/negative percentages.

-const limit = Number.parseInt(value, 10);
-return Number.isNaN(limit) ? undefined : limit;
+const limit = Number.parseInt(value, 10);
+if (Number.isNaN(limit) || limit <= 0) {
+  return undefined;              // or throw if you prefer
+}
+return limit;

Side note: this parsing logic now exists in multiple commands; extracting it to utils.internal.ts would improve maintainability.

🧹 Nitpick comments (1)
README.md (1)

276-279: Remove trailing colon in heading to satisfy MD026

#### Blocks-specific options:#### Blocks-specific options

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9346ef and a78c143.

📒 Files selected for processing (3)
  • README.md (1 hunks)
  • src/commands/blocks.ts (1 hunks)
  • src/data-loader.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/data-loader.ts (3)
src/utils/five-hour-blocks.ts (3)
  • FiveHourBlock (23-34)
  • LoadedUsageEntry (3-14)
  • identifyFiveHourBlocks (47-107)
src/pricing-fetcher.ts (1)
  • PricingFetcher (7-166)
src/logger.ts (1)
  • logger (5-5)
🪛 LanguageTool
README.md

[uncategorized] ~278-~278: Loose punctuation mark.
Context: ...ons: - -t, --token-limit <number|max>: Set token limit for quota warnings (use...

(UNLIKELY_OPENING_PUNCTUATION)

🪛 markdownlint-cli2 (0.17.2)
README.md

276-276: Trailing punctuation in heading
Punctuation: ':'

(MD026, no-trailing-punctuation)

🪛 ESLint
src/commands/blocks.ts

[error] 69-379: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 69-69: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 72-91: Unsafe assignment of an any value.

(ts/no-unsafe-assignment)


[error] 94-94: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 94-94: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 95-95: Unsafe member access .level on an error typed value.

(ts/no-unsafe-member-access)


[error] 99-99: Unsafe assignment of an any value.

(ts/no-unsafe-assignment)


[error] 99-99: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 100-100: Unsafe assignment of an any value.

(ts/no-unsafe-assignment)


[error] 100-100: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 102-102: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 103-103: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 107-107: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 107-107: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 108-108: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 111-111: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 111-111: Unsafe member access .warn on an error typed value.

(ts/no-unsafe-member-access)


[error] 113-113: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 113-113: Unsafe member access .exit on an error typed value.

(ts/no-unsafe-member-access)


[error] 118-118: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 127-127: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 127-127: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 128-128: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 128-128: Unsafe member access .info on an error typed value.

(ts/no-unsafe-member-access)


[error] 133-133: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 133-133: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 137-137: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 137-137: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 140-140: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 140-140: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 141-141: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 144-144: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 144-144: Unsafe member access .info on an error typed value.

(ts/no-unsafe-member-access)


[error] 146-146: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 146-146: Unsafe member access .exit on an error typed value.

(ts/no-unsafe-member-access)


[error] 150-150: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 150-150: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 173-173: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 175-175: Unsafe argument of type any assigned to a parameter of type string | undefined.

(ts/no-unsafe-argument)


[error] 175-175: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 192-192: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 196-196: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 196-196: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 200-200: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 200-200: Unsafe member access .warn on an error typed value.

(ts/no-unsafe-member-access)


[error] 201-201: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 201-201: Unsafe member access .exit on an error typed value.

(ts/no-unsafe-member-access)


[error] 206-206: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 206-206: Unsafe member access .box on an error typed value.

(ts/no-unsafe-member-access)


[error] 216-216: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 216-216: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 216-216: Unsafe member access .cyan on an error typed value.

(ts/no-unsafe-member-access)


[error] 216-216: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 216-216: Unsafe member access .yellow on an error typed value.

(ts/no-unsafe-member-access)


[error] 217-217: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 217-217: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 217-217: Unsafe member access .green on an error typed value.

(ts/no-unsafe-member-access)


[error] 219-219: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 219-219: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 219-219: Unsafe member access .bold on an error typed value.

(ts/no-unsafe-member-access)


[error] 220-220: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 221-221: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 222-222: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 225-225: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 225-225: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 225-225: Unsafe member access .bold on an error typed value.

(ts/no-unsafe-member-access)


[error] 226-226: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 227-227: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 231-231: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 231-231: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 231-231: Unsafe member access .bold on an error typed value.

(ts/no-unsafe-member-access)


[error] 232-232: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 233-233: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 235-235: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 237-237: Unsafe argument of type any assigned to a parameter of type string | undefined.

(ts/no-unsafe-argument)


[error] 237-237: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 242-246: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 243-243: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 243-243: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 245-245: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 245-245: Unsafe member access .yellow on an error typed value.

(ts/no-unsafe-member-access)


[error] 246-246: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 246-246: Unsafe member access .green on an error typed value.

(ts/no-unsafe-member-access)


[error] 248-248: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 248-248: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 248-248: Unsafe member access .bold on an error typed value.

(ts/no-unsafe-member-access)


[error] 249-249: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 250-250: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 251-251: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 252-252: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 259-259: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 259-259: Unsafe member access .box on an error typed value.

(ts/no-unsafe-member-access)


[error] 262-262: Unsafe argument of type any assigned to a parameter of type string | undefined.

(ts/no-unsafe-argument)


[error] 262-262: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 276-280: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 276-280: Unsafe construction of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 286-286: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 286-286: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 287-287: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 287-287: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 288-288: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 288-288: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 289-289: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 289-289: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 290-290: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 290-290: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 293-293: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 293-293: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 295-295: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 295-295: Unsafe member access .push on an error typed value.

(ts/no-unsafe-member-access)


[error] 300-300: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 300-300: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 300-300: Unsafe member access .green on an error typed value.

(ts/no-unsafe-member-access)


[error] 313-313: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 313-313: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 317-317: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 317-317: Unsafe member access .push on an error typed value.

(ts/no-unsafe-member-access)


[error] 325-327: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 327-327: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 327-327: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 331-333: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 333-333: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 333-333: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 336-336: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 336-336: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 336-336: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 337-337: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 337-337: Unsafe member access .blue on an error typed value.

(ts/no-unsafe-member-access)


[error] 343-343: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 343-343: Unsafe member access .push on an error typed value.

(ts/no-unsafe-member-access)


[error] 350-352: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 351-351: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 351-351: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 355-355: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 355-355: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 355-355: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 356-356: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 356-356: Unsafe member access .yellow on an error typed value.

(ts/no-unsafe-member-access)


[error] 369-369: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 369-369: Unsafe member access .push on an error typed value.

(ts/no-unsafe-member-access)


[error] 375-375: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 375-375: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 375-375: Unsafe member access .toString on an error typed value.

(ts/no-unsafe-member-access)

src/data-loader.ts

[error] 827-827: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 827-827: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 827-827: Unsafe member access .join on an error typed value.

(ts/no-unsafe-member-access)


[error] 828-831: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 828-828: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 829-829: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 833-833: Unsafe member access .length on an error typed value.

(ts/no-unsafe-member-access)


[error] 838-838: Unsafe argument of type error typed assigned to a parameter of type string[].

(ts/no-unsafe-argument)


[error] 841-841: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 853-853: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 853-853: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 854-857: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 854-857: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 854-856: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 854-855: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 855-855: Unsafe member access .trim on an error typed value.

(ts/no-unsafe-member-access)


[error] 856-856: Unsafe member access .split on an error typed value.

(ts/no-unsafe-member-access)


[error] 857-857: Unsafe member access .filter on an error typed value.

(ts/no-unsafe-member-access)


[error] 857-857: Unsafe member access .length on an any value.

(ts/no-unsafe-member-access)


[error] 861-861: Unsafe argument of type error typed assigned to a parameter of type string.

(ts/no-unsafe-argument)


[error] 862-862: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 862-862: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 862-862: Unsafe member access .safeParse on an error typed value.

(ts/no-unsafe-member-access)


[error] 863-863: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 863-863: Unsafe member access .success on an error typed value.

(ts/no-unsafe-member-access)


[error] 866-866: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 866-866: Unsafe member access .output on an error typed value.

(ts/no-unsafe-member-access)


[error] 878-880: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 880-880: Unsafe member access .costUSD on an error typed value.

(ts/no-unsafe-member-access)


[error] 883-883: Unsafe argument of type error typed assigned to a parameter of type string | number | Date.

(ts/no-unsafe-argument)


[error] 883-883: Unsafe member access .timestamp on an error typed value.

(ts/no-unsafe-member-access)


[error] 885-885: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 885-885: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 886-886: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 886-886: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 887-887: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 887-887: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 888-888: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 888-888: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 890-890: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 891-891: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 891-891: Unsafe member access .message on an error typed value.

(ts/no-unsafe-member-access)


[error] 892-892: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 892-892: Unsafe member access .version on an error typed value.

(ts/no-unsafe-member-access)


[error] 897-897: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 897-897: Unsafe member access .debug on an error typed value.

(ts/no-unsafe-member-access)

Remove rounding of costPerHour in calculateBurnRate() to maintain precision
for downstream calculations in projectBlockUsage(). The costPerHour value
is used for calculating projectedAdditionalCost, where premature rounding
can introduce cumulative errors. Display formatting is already handled
by formatCurrency() at the presentation layer.

This change ensures accurate cost projections for active blocks while
keeping the user-facing display properly formatted.
@ryoppippi ryoppippi requested a review from Copilot June 17, 2025 12:36

This comment was marked as outdated.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/utils/five-hour-blocks.ts (1)

70-72: Remove unreachable null-check on lastEntry.

currentBlockEntries always contains at least one element once we’re inside the else branch (we push the first entry when the block is opened).
The guard adds noise and can be safely deleted.

-			const lastEntry = currentBlockEntries.at(-1);
-			if (lastEntry == null) {
-				continue;
-			}
+			const lastEntry = currentBlockEntries.at(-1); // never null
🧹 Nitpick comments (2)
src/utils/five-hour-blocks.ts (2)

113-114: isActive may become stale shortly after computation.

now is captured once at the start of identifyFiveHourBlocks and passed to every block.
If the caller re-uses the resulting blocks for longer-running CLI output or caching, a block that was active may appear inactive minutes later (and vice-versa).

Consider computing isActive lazily (getter) or recalculating with the real-time clock when the value is consumed.


160-162: Gap block endTime stretches beyond 5 h window.

While the comment on endTime now acknowledges that gap blocks use nextActivityTime, consumers still need to branch on isGap to reason about block length.
A cleaner design is a discriminated union:

type NormalBlock = { isGap?: false; endTime: Date /* start + 5h */; ... }
type GapBlock    = { isGap: true;  gapEndTime: Date; endTime?: never; ... }

This enforces invariants at compile-time and removes implicit contracts from JSDoc.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a78c143 and 4a17ab8.

📒 Files selected for processing (1)
  • src/utils/five-hour-blocks.ts (1 hunks)

- Replace basic cli-table3 with ResponsiveTable for terminal width awareness
- Add compact date formatting for narrow terminals (< 120 columns)
- Implement abbreviated model name formatting like other commands
- Support complex table objects with content and hAlign properties
- Add terminal width detection and conditional compact formatting
- Maintain compatibility with existing projected/remaining row structure
- Enable word wrapping and responsive column sizing for better narrow terminal display

Fixes terminal display issues when using blocks command on narrow terminals
where model names and date information would cause table overflow and poor
readability. Now uses the same responsive table system as other commands.
Remove Math.round() from tokensPerMinute calculation in burnRate function to prevent
accuracy loss in projectBlockUsage calculations. The premature rounding was causing
inflated/deflated projections for long remaining time windows.

Keep tokensPerMinute as raw float for internal calculations and defer rounding
to display formatting only.
@ryoppippi ryoppippi requested a review from Copilot June 17, 2025 12:45
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Adds a new blocks command to report Claude Code usage in 5-hour billing windows, including active session monitoring, token-limit warnings, and JSON/table output formats.

  • Introduces core logic for grouping and projecting usage in src/utils/five-hour-blocks.ts
  • Implements the CLI blocks command with filters, projections, and table/JSON renderers
  • Integrates data loading (loadFiveHourBlockData) and updates documentation/README

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/utils/five-hour-blocks.ts Core block detection, burn-rate and projection utilities
src/utils.table.ts Extended table row type to support aligned object cells
src/data-loader.ts New loadFiveHourBlockData function to feed block logic
src/commands/index.ts Registers the new blocks subcommand
src/commands/blocks.ts Implements the blocks command and formatting logic
README.md Adds documentation for the 5-Hour Blocks Report
Comments suppressed due to low confidence (1)

src/utils.table.ts:1

  • [nitpick] The file name utils.table.ts diverges from the typical utils directory pattern. Consider renaming to utils/table.ts to keep file organization consistent.
import process from 'node:process';

return '-';
}
// Use abbreviated format like other commands
return compact ? formatModelsDisplay(models) : formatModelsDisplay(models);
Copy link

Copilot AI Jun 17, 2025

Choose a reason for hiding this comment

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

The conditional in formatModels is redundant since both branches call formatModelsDisplay(models). You can simplify this to return formatModelsDisplay(models); to improve readability.

Suggested change
return compact ? formatModelsDisplay(models) : formatModelsDisplay(models);
return formatModelsDisplay(models);

Copilot uses AI. Check for mistakes.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/commands/blocks.ts (1)

75-84: Zero / negative token limits still accepted – re-introduces divide-by-zero bug

parseTokenLimit no longer guards against <= 0, so "0" or "-10" silently propagate and later cause Infinity percentages.

-const limit = Number.parseInt(value, 10);
-return Number.isNaN(limit) ? undefined : limit;
+const limit = Number.parseInt(value, 10);
+if (Number.isNaN(limit) || limit <= 0) {
+  return undefined;          // or throw new Error('Token limit must be > 0');
+}
+return limit;
🧹 Nitpick comments (4)
src/utils.table.ts (3)

5-5: Consider extracting a reusable TableCell type alias

TableRow now embeds an inline object type for rich cells. Declaring a dedicated alias – e.g.
type TableCell = string | number | { content: string; hAlign?: 'left'|'right'|'center' }; – would keep the row type readable and avoid repeating the union elsewhere.


38-43: DRY up cell-to-string conversion

The anonymous callback is re-implementing the same “unwrap object cell → string” logic that also appears in isSeparatorRow. A tiny helper such as

function cellText(cell: TableCell): string {
  return typeof cell === 'object' && cell !== null && 'content' in cell
    ? cell.content
    : String(cell ?? '');
}

would remove duplication and make future tweaks (e.g. stripping ANSI codes) trivial.


154-159: isSeparatorRow can short-circuit & reuse a helper

Minor: once you have a cellText() helper (see comment above) this predicate collapses to

return row.every(c => {
  const txt = cellText(c);
  return txt === '' || /^+$/.test(txt);
});

reducing nesting and object checks.

src/commands/blocks.ts (1)

190-204: parseTokenLimit is called inside every .map – compute once

The JSON branch invokes parseTokenLimit for every block even though the value is identical for the whole command run. Parsing it once upfront (after maxTokensFromAll is known) both improves clarity and avoids unnecessary work.

-const jsonOutput = {
+const limit = parseTokenLimit(ctx.values.tokenLimit, maxTokensFromAll);
+const jsonOutput = {
   blocks: blocks.map(block => {
     …
-    tokenLimitStatus: projection != null && ctx.values.tokenLimit != null
+    tokenLimitStatus: projection != null && limit != null
         ? {
-            limit,
+            limit,
             projectedUsage: projection.totalTokens,
             percentUsed: (projection.totalTokens / limit) * 100,
             status: projection.totalTokens > limit
               ? 'exceeds'
               : projection.totalTokens > limit * 0.8 ? 'warning' : 'ok',
           }
         : undefined,
   }),
};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a17ab8 and bf87e0b.

📒 Files selected for processing (3)
  • src/commands/blocks.ts (1 hunks)
  • src/utils.table.ts (3 hunks)
  • src/utils/five-hour-blocks.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils/five-hour-blocks.ts
🧰 Additional context used
🪛 ESLint
src/commands/blocks.ts

[error] 86-400: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 86-86: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 89-108: Unsafe assignment of an any value.

(ts/no-unsafe-assignment)


[error] 111-111: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 111-111: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 112-112: Unsafe member access .level on an error typed value.

(ts/no-unsafe-member-access)


[error] 116-116: Unsafe assignment of an any value.

(ts/no-unsafe-assignment)


[error] 116-116: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 117-117: Unsafe assignment of an any value.

(ts/no-unsafe-assignment)


[error] 117-117: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 119-119: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 120-120: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 124-124: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 124-124: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 125-125: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 128-128: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 128-128: Unsafe member access .warn on an error typed value.

(ts/no-unsafe-member-access)


[error] 130-130: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 130-130: Unsafe member access .exit on an error typed value.

(ts/no-unsafe-member-access)


[error] 135-135: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 144-144: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 144-144: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 145-145: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 145-145: Unsafe member access .info on an error typed value.

(ts/no-unsafe-member-access)


[error] 150-150: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 150-150: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 154-154: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 154-154: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 157-157: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 157-157: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 158-158: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 161-161: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 161-161: Unsafe member access .info on an error typed value.

(ts/no-unsafe-member-access)


[error] 163-163: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 163-163: Unsafe member access .exit on an error typed value.

(ts/no-unsafe-member-access)


[error] 167-167: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 167-167: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 190-190: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 192-192: Unsafe argument of type any assigned to a parameter of type string | undefined.

(ts/no-unsafe-argument)


[error] 192-192: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 209-209: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 213-213: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 213-213: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 217-217: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 217-217: Unsafe member access .warn on an error typed value.

(ts/no-unsafe-member-access)


[error] 218-218: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 218-218: Unsafe member access .exit on an error typed value.

(ts/no-unsafe-member-access)


[error] 223-223: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 223-223: Unsafe member access .box on an error typed value.

(ts/no-unsafe-member-access)


[error] 233-233: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 233-233: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 233-233: Unsafe member access .cyan on an error typed value.

(ts/no-unsafe-member-access)


[error] 233-233: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 233-233: Unsafe member access .yellow on an error typed value.

(ts/no-unsafe-member-access)


[error] 234-234: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 234-234: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 234-234: Unsafe member access .green on an error typed value.

(ts/no-unsafe-member-access)


[error] 236-236: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 236-236: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 236-236: Unsafe member access .bold on an error typed value.

(ts/no-unsafe-member-access)


[error] 237-237: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 238-238: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 239-239: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 242-242: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 242-242: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 242-242: Unsafe member access .bold on an error typed value.

(ts/no-unsafe-member-access)


[error] 243-243: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 244-244: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 248-248: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 248-248: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 248-248: Unsafe member access .bold on an error typed value.

(ts/no-unsafe-member-access)


[error] 249-249: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 250-250: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 252-252: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 254-254: Unsafe argument of type any assigned to a parameter of type string | undefined.

(ts/no-unsafe-argument)


[error] 254-254: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 259-263: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 260-260: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 260-260: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 262-262: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 262-262: Unsafe member access .yellow on an error typed value.

(ts/no-unsafe-member-access)


[error] 263-263: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 263-263: Unsafe member access .green on an error typed value.

(ts/no-unsafe-member-access)


[error] 265-265: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 265-265: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 265-265: Unsafe member access .bold on an error typed value.

(ts/no-unsafe-member-access)


[error] 266-266: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 267-267: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 268-268: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 269-269: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 276-276: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 276-276: Unsafe member access .box on an error typed value.

(ts/no-unsafe-member-access)


[error] 279-279: Unsafe argument of type any assigned to a parameter of type string | undefined.

(ts/no-unsafe-argument)


[error] 279-279: Unsafe member access .values on an any value.

(ts/no-unsafe-member-access)


[error] 300-300: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 300-300: Unexpected any value in conditional. An explicit comparison or type conversion is required.

(ts/strict-boolean-expressions)


[error] 300-300: Unsafe member access .stdout on an error typed value.

(ts/no-unsafe-member-access)


[error] 307-307: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 307-307: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 308-308: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 308-308: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 309-309: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 309-309: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 310-310: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 310-310: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 313-313: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 313-313: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 315-315: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 315-315: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 316-316: Unsafe argument of type any[] assigned to a parameter of type TableRow.

(ts/no-unsafe-argument)


[error] 321-321: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 321-321: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 321-321: Unsafe member access .green on an error typed value.

(ts/no-unsafe-member-access)


[error] 334-334: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 334-334: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 338-338: Unsafe argument of type any[] assigned to a parameter of type TableRow.

(ts/no-unsafe-argument)


[error] 346-348: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 348-348: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 348-348: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 352-354: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 354-354: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 354-354: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 357-357: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 357-357: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 357-357: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 358-358: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 358-358: Unsafe member access .blue on an error typed value.

(ts/no-unsafe-member-access)


[error] 364-364: Unsafe argument of type any[] assigned to a parameter of type TableRow.

(ts/no-unsafe-argument)


[error] 371-373: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 372-372: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 372-372: Unsafe member access .red on an error typed value.

(ts/no-unsafe-member-access)


[error] 376-376: Unsafe assignment of an error typed value.

(ts/no-unsafe-assignment)


[error] 376-376: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 376-376: Unsafe member access .gray on an error typed value.

(ts/no-unsafe-member-access)


[error] 377-377: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)


[error] 377-377: Unsafe member access .yellow on an error typed value.

(ts/no-unsafe-member-access)


[error] 390-390: Unsafe argument of type any[] assigned to a parameter of type TableRow.

(ts/no-unsafe-argument)


[error] 396-396: Unsafe call of a(n) error type typed value.

(ts/no-unsafe-call)

Comment on lines +41 to +49
if (block.isActive) {
const now = new Date();
const elapsed = Math.round((now.getTime() - block.startTime.getTime()) / (1000 * 60));
const remaining = Math.round((block.endTime.getTime() - now.getTime()) / (1000 * 60));
const elapsedHours = Math.floor(elapsed / 60);
const elapsedMins = elapsed % 60;
const remainingHours = Math.floor(remaining / 60);
const remainingMins = remaining % 60;

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard against negative “remaining” minutes after block expiry

If the CLI is run a few seconds after the 5-hour window ended, remaining becomes negative and formatting shows -0h-X m. Protect with Math.max(0, remaining) before the hour/min calculations.

🤖 Prompt for AI Agents
In src/commands/blocks.ts around lines 41 to 49, the calculation of remaining
minutes can result in a negative value if the block has expired, causing
incorrect time formatting. To fix this, wrap the remaining minutes calculation
with Math.max(0, remaining) before computing remainingHours and remainingMins to
ensure the values never go below zero.

Add test coverage for all functions in src/utils/five-hour-blocks.ts:
- identifyFiveHourBlocks: Tests block creation, gap handling, sorting, aggregation
- calculateBurnRate: Tests rate calculation for active blocks
- projectBlockUsage: Tests usage projection based on burn rate
- filterRecentBlocks: Tests filtering by time range and active status

The tests cover edge cases including:
- Empty entries
- Gap blocks between sessions
- Different models in same block
- Null cost values
- Cache token handling
- Deduplication scenarios
Add comprehensive test coverage for the loadFiveHourBlockData function in data-loader.ts:
- Empty file handling
- Five-hour block identification and aggregation
- Cost calculation mode support (auto/calculate/display)
- Date range filtering (since/until parameters)
- Sort order handling (asc/desc)
- Deduplication of duplicate entries
- Invalid JSON line handling

Tests ensure the function properly loads JSONL files, processes them into
five-hour blocks using the identifyFiveHourBlocks utility, and applies
all filtering and sorting options correctly.
- Move src/utils/five-hour-blocks.ts to src/five-hour-blocks.internal.ts
- Move src/utils/five-hour-blocks.test.ts to src/five-hour-blocks.internal.test.ts
- Update test import to reference new location
- Maintain same functionality and interface

This consolidates internal utilities into a flat structure following
the .internal naming convention for better code organization.
- Update import path in src/commands/blocks.ts to use new flat structure
- Remove old files from src/utils/ directory
- Complete migration from utils directory to flat structure

This completes the refactoring to eliminate the utils directory
in favor of a flat structure with .internal naming convention.

Note: TypeScript errors will be fixed in follow-up commit.
- Fix unsafe type assertions and member access in blocks.ts
- Correct import path in data-loader.ts from utils/ to five-hour-blocks.internal.ts
- Add explicit type annotations for FiveHourBlock parameters
- Remove redundant Boolean() calls that caused lint warnings
- Use type assertions (as FiveHourBlock[]) for iterator variables
- All 81 ESLint errors reduced to 0 with proper type safety maintained
- Tests continue to pass with no functional changes to command behavior

This resolves the TypeScript strict mode compatibility issues while
preserving the existing CLI functionality and maintaining type safety.
- Add configurable constants for magic numbers (RECENT_DAYS_DEFAULT, WARNING_THRESHOLD, etc.)
- Complete type assertion fixes for remaining for...of loops
- Apply automated ESLint formatting improvements
- All lint errors resolved, tests passing, type checking clean
Copy link
Owner

@ryoppippi ryoppippi left a comment

Choose a reason for hiding this comment

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

LGTM

@ryoppippi ryoppippi merged commit 3984fea into ryoppippi:main Jun 17, 2025
7 checks passed
ryoppippi added a commit that referenced this pull request Jun 22, 2025
feat: add 5-hour blocks report command
@coderabbitai coderabbitai bot mentioned this pull request Sep 17, 2025
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.

5 hours sessions tracker

3 participants