Skip to content

Comments

feat(cli): improve quota display in /stats command and footer#19612

Open
spencer426 wants to merge 9 commits intomainfrom
feat/stats-quota-display-improvements
Open

feat(cli): improve quota display in /stats command and footer#19612
spencer426 wants to merge 9 commits intomainfrom
feat/stats-quota-display-improvements

Conversation

@spencer426
Copy link
Contributor

@spencer426 spencer426 commented Feb 20, 2026

Summary

Improve the visual styling and data visibility of API quota information in the /stats command and the CLI footer.
Screenshot 2026-02-20 173418

Details

  • Consistent Visibility: Updated /stats and /stats session to show available pooled quota summary even if no models have been used in the current session.
  • Design Alignment: Refined QuotaStatsInfo styling to match the requested design:
    • Bolder usage percentages for better readability.
    • Added commas before reset times.
    • Updated the /auth message to a more concise format.
  • Improved Formatting:
    • Standardized the "Usage left" column in the model usage table.
    • Wrapped reset info in parentheses with capitalized "Resets in" (e.g., (Resets in 1h 43m)).
    • Improved formatResetTime to be more resilient to different date formats and handle cases where the reset is imminent (showing < 1m) or in the past (showing Resetting...).
  • Layout Robustness: Adjusted column widths and removed truncation in StatsDisplay to ensure reset time information is always fully visible.
  • Resilient Rendering: Ensured informational quota text ("Usage limits span all sessions...") renders consistently for auto-models even when specific numeric data is missing.

Related Issues

N/A

How to Validate

  1. Run /stats or /stats session in an authenticated session.
  2. Verify the quota summary block appears with bold percentages and ", resets in" formatting.
  3. Verify the "Usage left" column in the model table shows percentages and (Resets in ...) info.
  4. Run tests: npm test packages/cli/src/ui/components/StatsDisplay.test.tsx

Pre-Merge Checklist

  • Updated relevant documentation and README (if needed)
  • Added/updated tests (if needed)
  • Noted breaking changes (if any)
  • Validated on required platforms/methods:
    • MacOS
    • Windows
    • Linux
      • npm run

Fixes #19584

@spencer426 spencer426 requested a review from a team as a code owner February 20, 2026 04:57
@gemini-cli
Copy link
Contributor

gemini-cli bot commented Feb 20, 2026

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!

@gemini-cli
Copy link
Contributor

gemini-cli bot commented Feb 20, 2026

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 Fixes #123 or Related to #123), it will be automatically reopened.

How to link an issue:
Add a keyword followed by the issue number (e.g., Fixes #123) in the description of your pull request. For more details on supported keywords and how linking works, please refer to the GitHub Documentation on linking pull requests to issues.

Thank you for your understanding and for being a part of our community!

@gemini-cli gemini-cli bot closed this Feb 20, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @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 /stats command and the CLI footer. Users will benefit from more readable, comprehensive, and reliably formatted quota data, making it easier to understand their current API limits and when they will refresh.

Highlights

  • Enhanced Quota Visibility: The /stats command and CLI footer now consistently display pooled API quota summaries, even when no models have been used in the current session, ensuring users always see their overall quota status.
  • Improved UI Formatting: Quota usage percentages are now bolder for better readability, reset times include commas, and the /auth message has been made more concise. The 'Usage left' column is standardized, and reset information is wrapped in parentheses with 'Resets in' capitalized (e.g., '(Resets in 1h 43m)').
  • Robust Reset Time Handling: The formatResetTime utility has been made more resilient, capable of parsing various date formats (ISO strings, Unix timestamps). It now displays '< 1m' for imminent resets and '(Resetting...)' for past or invalid reset times.
  • Optimized Layout and Rendering: Column widths in the StatsDisplay have been adjusted, and truncation removed, to ensure that reset time information is always fully visible. Informational quota text for auto-models now renders consistently, even if specific numeric data is missing.
  • Pooled Quota Reset Logic Update: The logic for determining the pooled quota reset time has been updated to select the nearest future reset time, rather than the furthest, providing a more immediate and relevant indication of when quotas will refresh.
Changelog
  • packages/cli/src/ui/components/QuotaStatsInfo.tsx
    • Modified rendering logic to display quota information more consistently, even when remaining or limit values are null or undefined.
    • Applied bold styling to usage percentages for improved readability.
    • Updated the /auth message to be more concise.
    • Removed the explicit 'Usage limit' line to streamline the display.
  • packages/cli/src/ui/components/StatsDisplay.test.tsx
    • Updated test assertions to reflect the new 'Usage left' column header and the parenthesized 'Resets in' format.
    • Added new test cases to verify pooled quota rendering when no models have been used.
    • Included a test case to ensure model table quota percentages render correctly even when resetTime is absent.
    • Updated existing snapshot tests to align with the revised UI output.
  • packages/cli/src/ui/components/StatsDisplay.tsx
    • Adjusted the ModelUsageTable rendering condition to show pooled quota information even if no specific model rows exist.
    • Modified column widths for 'name' and 'usage limit' to improve layout and prevent truncation.
    • Renamed the 'Usage remaining' column header to 'Usage left'.
    • Implemented parenthesized and capitalized 'Resets in' formatting for reset times within the model usage table.
    • Ensured the QuotaStatsInfo component is displayed for auto-models, providing consistent informational text.
  • packages/cli/src/ui/components/snapshots/StatsDisplay.test.tsx.snap
    • Updated multiple UI snapshots to reflect changes in quota display, formatting, and layout.
  • packages/cli/src/ui/utils/formatters.ts
    • Enhanced formatResetTime to robustly parse various date formats, including ISO strings and Unix timestamps.
    • Introduced logic to return '(Resetting...)' for past or invalid reset times.
    • Added a display for '< 1m' to indicate imminent quota resets.
    • Refactored the internal logic for constructing the time string.
  • packages/core/src/config/config.ts
    • Updated the pooled quota reset time calculation to select the nearest future reset time from available quotas, rather than the furthest.
Ignored Files
  • Ignored by pattern: .gemini/** (1)
    • .gemini/skills/pr-address-comments/scripts/fetch-pr-info.js
Activity
  • The pull request author, spencer426, created this PR to implement the described feature and improvements.
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 by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

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 pull request 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 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

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

Copy link
Contributor

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

Choose a reason for hiding this comment

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

Code Review

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.

Comment on lines 60 to 62
: remaining !== undefined && remaining !== null
? `${remaining.toLocaleString()}`
: 'Limit reached'}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

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.

Suggested change
: remaining !== undefined && remaining !== null
? `${remaining.toLocaleString()}`
: 'Limit reached'}
? `${percentage.toFixed(0)}%`
: 'Limit reached'

Comment on lines 66 to 71
{percentage !== undefined ||
(remaining !== undefined && remaining !== null)
? ' usage remaining'
: ''}
</Text>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

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.

Suggested change
{percentage !== undefined ||
(remaining !== undefined && remaining !== null)
? ' usage remaining'
: ''}
</Text>
)}
<Text>
{' usage remaining'}
</Text>

Comment on lines 165 to 170
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;
Copy link
Contributor

Choose a reason for hiding this comment

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

high

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.

Comment on lines 350 to 367
<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>
Copy link
Contributor

Choose a reason for hiding this comment

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

high

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>

Comment on lines 105 to 110
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

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.

Comment on lines 131 to 140
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';
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

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`;
  }

Comment on lines 1332 to 1335
// 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];
Copy link
Contributor

Choose a reason for hiding this comment

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

high

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.

Suggested change
// 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();

@spencer426 spencer426 reopened this Feb 20, 2026
@github-actions
Copy link

github-actions bot commented Feb 20, 2026

Size Change: +546 B (0%)

Total Size: 25.2 MB

ℹ️ View Unchanged
Filename Size Change
./bundle/gemini.js 25.2 MB +546 B (0%)
./bundle/sandbox-macos-permissive-open.sb 890 B 0 B
./bundle/sandbox-macos-permissive-proxied.sb 1.31 kB 0 B
./bundle/sandbox-macos-restrictive-open.sb 3.36 kB 0 B
./bundle/sandbox-macos-restrictive-proxied.sb 3.56 kB 0 B
./bundle/sandbox-macos-strict-open.sb 4.82 kB 0 B
./bundle/sandbox-macos-strict-proxied.sb 5.02 kB 0 B

compressed-size-action

@gemini-code-assist
Copy link
Contributor

Thank you for addressing all the feedback! I've reviewed the changes and can confirm that:

  • The conditional rendering logic in QuotaStatsInfo has been simplified, improving readability.
  • The chronological sorting of pooled reset times in Config now correctly uses Date objects to determine the nearest future reset.
  • The Unix timestamp parsing heuristic in formatResetTime utility is now clearly documented.
  • The issues with redundant prefixes and double parentheses in the quota display have been resolved, leading to a cleaner output.
  • The changes have been verified with updated unit tests in both the CLI and Core packages.

Great work on these improvements!

@gemini-cli gemini-cli bot added the area/core Issues related to User Interface, OS Support, Core Functionality label Feb 20, 2026
- 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.
@spencer426 spencer426 force-pushed the feat/stats-quota-display-improvements branch from fee783d to aa68a97 Compare February 20, 2026 21:55
@spencer426
Copy link
Contributor Author

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:

  • Consolidated Logic: Duplicated formatting logic for reset times has been moved into a shared utility, ensuring consistency between the /stats command and the CLI footer.
  • Improved Robustness: Unix timestamp parsing is now more resilient and uses clear constants for its heuristics.
  • UI Consistency: Styling for inactive models in the usage table has been standardized to use secondary text colors across all columns.
  • Conservative Defaults: Maintained existing quota color thresholds and conservative reset logic to avoid unexpected UX shifts.
  • Test Coverage: All relevant tests in StatsDisplay.test.tsx and ModelStatsDisplay.test.tsx are passing.

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.

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

Labels

area/core Issues related to User Interface, OS Support, Core Functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant