feat(cli): improve quota display in /stats command and footer#19612
feat(cli): improve quota display in /stats command and footer#19612spencer426 wants to merge 9 commits intomainfrom
Conversation
|
Hi @spencer426, thank you so much for your contribution to Gemini CLI! We really appreciate the time and effort you've put into this. We're making some updates to our contribution process to improve how we track and review changes. Please take a moment to review our recent discussion post: Improving Our Contribution Process & Introducing New Guidelines. Key Update: Starting January 26, 2026, the Gemini CLI project will require all pull requests to be associated with an existing issue. Any pull requests not linked to an issue by that date will be automatically closed. Thank you for your understanding and for being a part of our community! |
|
Hi there! Thank you for your contribution to Gemini CLI. To improve our contribution process and better track changes, we now require all pull requests to be associated with an existing issue, as announced in our recent discussion and as detailed in our CONTRIBUTING.md. This pull request is being closed because it is not currently linked to an issue. Once you have updated the description of this PR to link an issue (e.g., by adding How to link an issue: Thank you for your understanding and for being a part of our community! |
Summary of ChangesHello @spencer426, 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 user experience for viewing API quota information within the CLI. The changes focus on improving the clarity, consistency, and robustness of how quota usage and reset times are displayed across the Highlights
Changelog
Ignored Files
Activity
Using Gemini Code AssistThe 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 by creating a comment using either
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 Limitations & Feedback Gemini Code Assist 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. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. 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
|
There was a problem hiding this comment.
Code Review
The pull request significantly improves the display of API quota information in the /stats command and the CLI footer. Key changes include consistent visibility of pooled quota, refined styling for better readability, improved formatting for usage percentages and reset times, and adjusted column widths for robustness. The changes enhance the user experience by providing clearer and more comprehensive quota insights. The test coverage has also been updated to reflect these changes.
| : remaining !== undefined && remaining !== null | ||
| ? `${remaining.toLocaleString()}` | ||
| : 'Limit reached'} |
There was a problem hiding this comment.
The logic for displaying 'Limit reached' seems to be duplicated and could be simplified. The condition remaining !== undefined && remaining !== null is already covered by the outer hasData check, and remaining === 0 is handled explicitly. Consider refactoring to avoid redundant checks and improve readability.
| : remaining !== undefined && remaining !== null | |
| ? `${remaining.toLocaleString()}` | |
| : 'Limit reached'} | |
| ? `${percentage.toFixed(0)}%` | |
| : 'Limit reached' |
| {percentage !== undefined || | ||
| (remaining !== undefined && remaining !== null) | ||
| ? ' usage remaining' | ||
| : ''} | ||
| </Text> | ||
| )} |
There was a problem hiding this comment.
The conditional rendering for 'usage remaining' is overly complex. The percentage !== undefined condition already implies remaining !== undefined && remaining !== null due to how percentage is calculated. This can be simplified to make the code more readable.
| {percentage !== undefined || | |
| (remaining !== undefined && remaining !== null) | |
| ? ' usage remaining' | |
| : ''} | |
| </Text> | |
| )} | |
| <Text> | |
| {' usage remaining'} | |
| </Text> |
| const nameWidth = 22; | ||
| const requestsWidth = 7; | ||
| const uncachedWidth = 15; | ||
| const cachedWidth = 14; | ||
| const outputTokensWidth = 15; | ||
| const usageLimitWidth = showQuotaColumn ? 28 : 0; | ||
| const usageLimitWidth = showQuotaColumn ? 40 : 0; |
There was a problem hiding this comment.
The hardcoded widths for nameWidth and usageLimitWidth might lead to layout issues on terminals with different sizes or when model names are very long. While wrap="truncate-end" is used for model names, the overall table layout could become misaligned. Consider using a more dynamic width calculation or a flexible layout approach to ensure responsiveness across various terminal environments.
| <Box width={usageLimitWidth} justifyContent="flex-end"> | ||
| {row.bucket && row.bucket.remainingFraction != null && ( | ||
| <Text color={theme.text.secondary}> | ||
| {(row.bucket.remainingFraction * 100).toFixed(1)}% | ||
| {row.bucket.resetTime && ( | ||
| <Text color={theme.text.secondary}> | ||
| {' '} | ||
| ( | ||
| {formatResetTime(row.bucket.resetTime).replace( | ||
| 'resets in', | ||
| 'Resets in', | ||
| )} | ||
| ) | ||
| </Text> | ||
| )} | ||
| </Text> | ||
| )} | ||
| </Box> |
There was a problem hiding this comment.
The formatResetTime function is called and then replace is used to capitalize "Resets in". It would be more efficient and cleaner to modify formatResetTime directly to return the desired capitalized string, or to pass a flag to it to control capitalization. This avoids string manipulation after formatting.
<Box width={usageLimitWidth} justifyContent="flex-end">
{row.bucket && row.bucket.remainingFraction != null && (
<Text color={theme.text.secondary}>
{(row.bucket.remainingFraction * 100).toFixed(1)}%
{row.bucket.resetTime && (
<Text color={theme.text.secondary}>
{' '}
({formatResetTime(row.bucket.resetTime, true)})
</Text>
)}
</Text>
)}
</Box>
| if (isNaN(date.getTime())) { | ||
| const timestamp = parseInt(resetTime, 10); | ||
| if (!isNaN(timestamp)) { | ||
| // Could be seconds or milliseconds. If < 10^12, likely seconds. | ||
| date = new Date(timestamp < 10000000000 ? timestamp * 1000 : timestamp); | ||
| } |
There was a problem hiding this comment.
The logic for parsing Unix timestamps assumes that a timestamp less than 10^12 is in seconds, and otherwise in milliseconds. While this is a common heuristic, it's not foolproof and could lead to incorrect parsing for very large second-based timestamps or very small millisecond-based timestamps. It would be more robust to explicitly check for the expected format if possible, or to document this assumption clearly.
| let timeStr = ''; | ||
| if (hours > 0 && minutes > 0) { | ||
| return `resets in ${fmt(hours, 'hour')} ${fmt(minutes, 'minute')}`; | ||
| timeStr = `${fmt(hours, 'hour')} ${fmt(minutes, 'minute')}`; | ||
| } else if (hours > 0) { | ||
| return `resets in ${fmt(hours, 'hour')}`; | ||
| timeStr = fmt(hours, 'hour'); | ||
| } else if (minutes > 0) { | ||
| timeStr = fmt(minutes, 'minute'); | ||
| } else { | ||
| timeStr = '< 1m'; | ||
| } |
There was a problem hiding this comment.
The timeStr variable is initialized as an empty string and then conditionally assigned. This pattern can sometimes be simplified by directly returning the formatted string within each conditional block, or by ensuring timeStr always has a default value that makes sense if none of the conditions are met. In this case, the final return statement handles the default, but the intermediate assignments could be streamlined.
if (hours > 0 && minutes > 0) {
return `resets in ${fmt(hours, 'hour')} ${fmt(minutes, 'minute')}`;
} else if (hours > 0) {
return `resets in ${fmt(hours, 'hour')}`;
} else if (minutes > 0) {
return `resets in ${fmt(minutes, 'minute')}`;
} else {
return `resets in < 1m`;
}
packages/core/src/config/config.ts
Outdated
| // For reset time, take the one that is nearest in the future (soonest reset) | ||
| const resetTime = [proQuota?.resetTime, flashQuota?.resetTime] | ||
| .filter((t): t is string => !!t) | ||
| .sort() | ||
| .reverse()[0]; | ||
| .sort()[0]; |
There was a problem hiding this comment.
The comment states to take the "nearest in the future (soonest reset)" for resetTime, but the .sort() method without a custom comparator will sort lexicographically, which might not correctly identify the soonest date if the date strings are not in a consistent, sortable format (e.g., ISO 8601). It's safer to convert to Date objects and compare their getTime() values to ensure correct chronological sorting.
| // For reset time, take the one that is nearest in the future (soonest reset) | |
| const resetTime = [proQuota?.resetTime, flashQuota?.resetTime] | |
| .filter((t): t is string => !!t) | |
| .sort() | |
| .reverse()[0]; | |
| .sort()[0]; | |
| const resetTime = [proQuota?.resetTime, flashQuota?.resetTime] | |
| .filter((t): t is string => !!t) | |
| .map((t) => new Date(t)) | |
| .sort((a, b) => a.getTime() - b.getTime())[0] | |
| ?.toISOString(); |
|
Size Change: +546 B (0%) Total Size: 25.2 MB ℹ️ View Unchanged
|
|
Thank you for addressing all the feedback! I've reviewed the changes and can confirm that:
Great work on these improvements! |
- Updates /stats and /stats session to show available quota even without usage.
- Refines QuotaStatsInfo styling to match design: bold percentages and improved spacing.
- Fixes reset time formatting ('Resets in') and visibility in the model usage table.
- Standardizes column widths in StatsDisplay to prevent truncation of quota info.
- Improves formatResetTime resilience to handle various date formats and small offsets.
- Ensures informational quota text renders consistently for auto-models.
- Reverts formatResetTime to return only the raw duration string. - Moves 'resets in' prefix logic to call sites to prevent duplication. - Fixes double parentheses in StatsDisplay for imminent resets. - Removes brittle string replacement in favor of direct formatting.
- Simplifies conditional rendering logic in QuotaStatsInfo. - Improves chronological sorting of reset times in Config using Date objects. - Documents the Unix timestamp parsing heuristic in formatResetTime. - Streamlines intermediate string assignments in the formatter.
fee783d to
aa68a97
Compare
|
The PR successfully improves the visibility and styling of API quota information. The intent to provide a more consistent and readable stats display is well-executed. Key improvements and best practices followed:
One minor observation: The table layout continues to use hardcoded column widths. While this is consistent with existing patterns in the codebase for these specific components, future refactors might consider more dynamic layouts if terminal window resizing becomes a priority. Overall, the code is clean, follows project conventions, and significantly improves the user experience for quota tracking. |
Summary
Improve the visual styling and data visibility of API quota information in the

/statscommand and the CLI footer.Details
/statsand/stats sessionto show available pooled quota summary even if no models have been used in the current session.QuotaStatsInfostyling to match the requested design:/authmessage to a more concise format.(Resets in 1h 43m)).formatResetTimeto be more resilient to different date formats and handle cases where the reset is imminent (showing< 1m) or in the past (showingResetting...).StatsDisplayto ensure reset time information is always fully visible.Related Issues
N/A
How to Validate
/statsor/stats sessionin an authenticated session.(Resets in ...)info.npm test packages/cli/src/ui/components/StatsDisplay.test.tsxPre-Merge Checklist
Fixes #19584