-
-
Notifications
You must be signed in to change notification settings - Fork 317
feat: add 5-hour blocks report command #80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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]>
|
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 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. 📒 Files selected for processing (5)
WalkthroughA new CLI feature and supporting utilities were added to track and report Claude usage in 5-hour billing blocks. This includes a new Changes
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)
Assessment against linked issues
Assessment against linked issues: Out-of-scope changesNo out-of-scope changes found. Poem
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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
blockscommand 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
blockscommand 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 insrc/data-loader.tshas 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
-
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. ↩
There was a problem hiding this 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.
src/commands/blocks.ts
Outdated
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/data-loader.ts
Outdated
| catch { | ||
| // Skip invalid JSON lines | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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))}`);
}There was a problem hiding this 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
calculateBurnRaterounds bothtokensPerMinuteandcostPerHour, thenprojectBlockUsagemultiplies these rounded numbers to estimate totals. On long remaining windows this can introduce noticeable drift.Return full-precision numbers from
calculateBurnRateand 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 producenull; consider minimum window guardFor blocks where the first and last entry share the same minute,
durationMinutesbecomes0, so burn-rate isnull.
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 (
createInterfacefromreadline/promisesor 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 calculateCostForEntryis executed inside the tight inner loop, forcing strictly sequential processing. IfPricingFetcherinvolves I/O (e.g. remote price fetch), total load time grows linearly with message count.Batch-fetch prices for distinct models once, or make
calculateCostForEntrysynchronous 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/maxhandling 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
tableHeadersandtableAlignsmust 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
⛔ Files ignored due to path filters (1)
package-lock.jsonis 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 registeredThe 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--ordermatch 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
There was a problem hiding this 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-limitis 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 outparseTokenLimitto 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:--activesilently 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 showsblocks[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-limitvalues.If the user passes an invalid string (e.g.
-t foo),parseTokenLimitreturnsundefined, 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
📒 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| modelsSet.add(entry.model); | |
| // Skip synthetic model | |
| if (entry.model !== '<synthetic>') { | |
| modelsSet.add(entry.model); | |
| } |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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:
Lines 141 to 144 in 486b74d
// Skip synthetic model if (modelName === '<synthetic>') { continue; } Lines 179 to 182 in 486b74d
// Skip synthetic model if (breakdown.modelName === '<synthetic>') { continue; } 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>'))]; } Lines 779 to 782 in 486b74d
// Skip synthetic model if (model !== '<synthetic>') { modelsSet.add(model); }
There was a problem hiding this comment.
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>'
|
@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. |
Co-authored-by: Alberto Leal <[email protected]>
There was a problem hiding this 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 commandsThis 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
📒 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 validactualTokenLimitAll
%displays (percentage,remainingPercent, etc.) should skip or visibly mark the field whenactualTokenLimitisundefined. Current guards prevent most cases, but edge-paths still exist (e.g., user passes-t foo). Consider early-returning with a warning whenparseTokenLimitfails, to avoid partial/incorrect tables.
167-178: Unchecked division bylimitwhen it isundefinedor0Even with the guard above,
limitis re-evaluated inline and could still beundefinedif parsing fails, causingprojection.totalTokens / limitto evaluate toInfinityorNaN. 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.
|
OK I start reviewing!! |
commit: |
There was a problem hiding this 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:parseTokenLimitstill accepts 0 / negative numbers — divide-by-zero riskA prior review already flagged this, but the implementation was not updated.
0(or<0) propagates to%calculations and JSON projection (projection.totalTokens / limit) causingInfinity, NaN, or runtimeZeroDivisionErrorequivalents.- 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: ReusefilterByDateRange()instead of open-coded string compareThe bespoke date check duplicates the earlier
filterByDateRangehelper 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 especiallytokenLimitflags plusparseTokenLimit()logic have been duplicated once already (see previous review). Move them intosharedCommandConfigor a tinyblocks-args.tsto avoid repeating validation & help-text in future commands.README.md (1)
262-267: Remove trailing colon in heading to satisfymarkdownlint(MD026)Current:
#### Blocks-specific options:Recommended:
#### Blocks-specific optionsVery minor, but keeps CI/preview clean.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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 againstundefined/ missing limitEven after fixing
parseTokenLimit,percentUsedis evaluated without ensuringlimitis defined (it might beundefinedif user omits-t). Wrap the division:- percentUsed: (projection.totalTokens / limit) * 100, + percentUsed: limit != null ? (projection.totalTokens / limit) * 100 : undefined,Prevents accidental
NaNin JSON output.Likely an incorrect or invalid review comment.
| 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) { |
There was a problem hiding this comment.
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.
|
@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.
…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
There was a problem hiding this 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 helperThe block between lines 852-899 is functionally identical to the loops inside
loadDailyUsageData()andloadSessionData().
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 % calculationsReturning
0(or a negative number) is treated later as a valid limit, producingInfinity/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.tswould 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
📒 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.
There was a problem hiding this 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 onlastEntry.
currentBlockEntriesalways contains at least one element once we’re inside theelsebranch (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:isActivemay become stale shortly after computation.
nowis captured once at the start ofidentifyFiveHourBlocksand 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
isActivelazily (getter) or recalculating with the real-time clock when the value is consumed.
160-162: Gap blockendTimestretches beyond 5 h window.While the comment on
endTimenow acknowledges that gap blocks usenextActivityTime, consumers still need to branch onisGapto 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.
- 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.
There was a problem hiding this 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
blockscommand 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.tsdiverges from the typicalutilsdirectory pattern. Consider renaming toutils/table.tsto keep file organization consistent.
import process from 'node:process';
| return '-'; | ||
| } | ||
| // Use abbreviated format like other commands | ||
| return compact ? formatModelsDisplay(models) : formatModelsDisplay(models); |
Copilot
AI
Jun 17, 2025
There was a problem hiding this comment.
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.
| return compact ? formatModelsDisplay(models) : formatModelsDisplay(models); | |
| return formatModelsDisplay(models); |
There was a problem hiding this 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
parseTokenLimitno longer guards against<= 0, so"0"or"-10"silently propagate and later causeInfinitypercentages.-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 reusableTableCelltype alias
TableRownow 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 conversionThe anonymous callback is re-implementing the same “unwrap object cell → string” logic that also appears in
isSeparatorRow. A tiny helper such asfunction 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:isSeparatorRowcan short-circuit & reuse a helperMinor: once you have a
cellText()helper (see comment above) this predicate collapses toreturn row.every(c => { const txt = cellText(c); return txt === '' || /^─+$/.test(txt); });reducing nesting and object checks.
src/commands/blocks.ts (1)
190-204:parseTokenLimitis called inside every.map– compute onceThe JSON branch invokes
parseTokenLimitfor every block even though the value is identical for the whole command run. Parsing it once upfront (aftermaxTokensFromAllis 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
📒 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)
| 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; | ||
|
|
There was a problem hiding this comment.
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
ryoppippi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
feat: add 5-hour blocks report command
Summary
This PR implements a new
blockscommand to analyze Claude Code usage in 5-hour billing windows as requested in #23.Features
-t <number>or use-t maxto automatically use highest previous block--recentflag shows only last 3 days of blocks--activeflag shows detailed view of current active blockImplementation Details
src/utils/five-hour-blocks.tshandles block detection and calculationssrc/commands/blocks.tswith table and JSON output formatsExample Output
Testing
Fixes #23
🤖 Generated with Claude Code
Co-Authored-By: @a-c-m
Summary by CodeRabbit