Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions src/segments/claude.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type ClaudeData struct {
SessionID string `json:"session_id"`
ContextWindow ClaudeContextWindow `json:"context_window"`
Cost ClaudeCost `json:"cost"`
RateLimits *ClaudeRateLimits `json:"rate_limits,omitempty"`
}

// ClaudeModel represents the AI model information
Expand Down Expand Up @@ -59,6 +60,18 @@ type ClaudeCurrentUsage struct {
CacheReadInputTokens int `json:"cache_read_input_tokens"`
}

// ClaudeRateLimits represents Claude.ai subscription usage limits
type ClaudeRateLimits struct {
FiveHour *ClaudeRateWindow `json:"five_hour,omitempty"`
SevenDay *ClaudeRateWindow `json:"seven_day,omitempty"`
}

// ClaudeRateWindow represents a single rate limit window
type ClaudeRateWindow struct {
UsedPercentage float64 `json:"used_percentage"`
ResetsAt int64 `json:"resets_at"`
}

const (
thousand = 1000.0
million = 1000000.0
Expand Down Expand Up @@ -173,3 +186,48 @@ func (c *Claude) FormattedTokens() string {

return fmt.Sprintf("%.1fM", float64(currentTokens)/million)
}

// FiveHourPercent returns the 5-hour rate limit usage as a Percentage.
// Returns 0 when rate limit data is not available.
func (c *Claude) FiveHourPercent() text.Percentage {
if c.RateLimits == nil || c.RateLimits.FiveHour == nil {
return 0
}

percent := int(c.RateLimits.FiveHour.UsedPercentage + 0.5)
if percent > 100 {
return 100
}

Comment on lines +197 to +201
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

The float-to-int conversion here doesn’t clamp the lower bound and can yield negative percentages if used_percentage is ever < 0 (which then affects Percentage.String() output). Consider clamping to the full valid range (0-100) after rounding, similar to TokenUsagePercent.

Copilot uses AI. Check for mistakes.
return text.Percentage(percent)
}

// SevenDayPercent returns the 7-day rate limit usage as a Percentage.
// Returns 0 when rate limit data is not available.
func (c *Claude) SevenDayPercent() text.Percentage {
if c.RateLimits == nil || c.RateLimits.SevenDay == nil {
return 0
}

percent := int(c.RateLimits.SevenDay.UsedPercentage + 0.5)
if percent > 100 {
return 100
}

Comment on lines +212 to +216
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

Same as FiveHourPercent: after rounding, clamp the result to the full valid range (0-100) so templates using .SevenDayPercent.String can’t emit negative values if unexpected data is received.

Copilot uses AI. Check for mistakes.
return text.Percentage(percent)
}

// HasRateLimits returns true when rate limit data is available.
func (c *Claude) HasRateLimits() bool {
return c.RateLimits != nil
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

HasRateLimits currently returns true whenever the rate_limits object is present, even if both windows are nil (e.g. JSON contains rate_limits: {}), which would make {{ if .HasRateLimits }} blocks render 0% values. Consider defining this as “at least one window is available” (e.g., HasFiveHourLimit || HasSevenDayLimit) to match the intent of conditional display.

Suggested change
return c.RateLimits != nil
return c.HasFiveHourLimit() || c.HasSevenDayLimit()

Copilot uses AI. Check for mistakes.
}

// HasFiveHourLimit returns true when the 5-hour rate limit window is available.
func (c *Claude) HasFiveHourLimit() bool {
return c.RateLimits != nil && c.RateLimits.FiveHour != nil
}
Comment on lines +225 to +228
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

There are unit tests for HasRateLimits, but none for the newly added HasFiveHourLimit / HasSevenDayLimit helpers. Adding simple table-driven tests would guard the template-facing API behavior.

Copilot uses AI. Check for mistakes.

// HasSevenDayLimit returns true when the 7-day rate limit window is available.
func (c *Claude) HasSevenDayLimit() bool {
return c.RateLimits != nil && c.RateLimits.SevenDay != nil
}
128 changes: 128 additions & 0 deletions src/segments/claude_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,134 @@ func TestClaudeTokenUsagePercent(t *testing.T) {
}
}

func TestClaudeFiveHourPercent(t *testing.T) {
cases := []struct {
Case string
RateLimits *ClaudeRateLimits
ExpectedPercent text.Percentage
}{
{
Case: "Nil rate limits",
RateLimits: nil,
ExpectedPercent: 0,
},
{
Case: "Nil five hour window",
RateLimits: &ClaudeRateLimits{},
ExpectedPercent: 0,
},
{
Case: "Zero usage",
RateLimits: &ClaudeRateLimits{
FiveHour: &ClaudeRateWindow{UsedPercentage: 0, ResetsAt: 1711612800},
},
ExpectedPercent: 0,
},
{
Case: "Normal usage",
RateLimits: &ClaudeRateLimits{
FiveHour: &ClaudeRateWindow{UsedPercentage: 45.3, ResetsAt: 1711612800},
},
ExpectedPercent: 45,
},
{
Case: "High usage rounds up",
RateLimits: &ClaudeRateLimits{
FiveHour: &ClaudeRateWindow{UsedPercentage: 79.6, ResetsAt: 1711612800},
},
ExpectedPercent: 80,
},
{
Case: "Over 100 capped",
RateLimits: &ClaudeRateLimits{
FiveHour: &ClaudeRateWindow{UsedPercentage: 150, ResetsAt: 1711612800},
},
ExpectedPercent: 100,
},
}

for _, tc := range cases {
claude := &Claude{}
claude.RateLimits = tc.RateLimits

percent := claude.FiveHourPercent()
assert.Equal(t, tc.ExpectedPercent, percent, tc.Case)
}
}

func TestClaudeSevenDayPercent(t *testing.T) {
cases := []struct {
Case string
RateLimits *ClaudeRateLimits
ExpectedPercent text.Percentage
}{
{
Case: "Nil rate limits",
RateLimits: nil,
ExpectedPercent: 0,
},
{
Case: "Nil seven day window",
RateLimits: &ClaudeRateLimits{},
ExpectedPercent: 0,
},
{
Case: "Normal usage",
RateLimits: &ClaudeRateLimits{
SevenDay: &ClaudeRateWindow{UsedPercentage: 12.7, ResetsAt: 1711612800},
},
ExpectedPercent: 13,
},
{
Case: "Over 100 capped",
RateLimits: &ClaudeRateLimits{
SevenDay: &ClaudeRateWindow{UsedPercentage: 200, ResetsAt: 1711612800},
},
ExpectedPercent: 100,
},
}

for _, tc := range cases {
claude := &Claude{}
claude.RateLimits = tc.RateLimits

percent := claude.SevenDayPercent()
assert.Equal(t, tc.ExpectedPercent, percent, tc.Case)
}
}

func TestClaudeHasRateLimits(t *testing.T) {
cases := []struct {
Case string
RateLimits *ClaudeRateLimits
Expected bool
}{
{
Case: "Nil rate limits",
RateLimits: nil,
Expected: false,
},
{
Case: "Empty rate limits",
RateLimits: &ClaudeRateLimits{},
Expected: true,
},
{
Case: "With five hour",
RateLimits: &ClaudeRateLimits{
FiveHour: &ClaudeRateWindow{UsedPercentage: 10},
},
Expected: true,
},
}

for _, tc := range cases {
claude := &Claude{}
claude.RateLimits = tc.RateLimits
assert.Equal(t, tc.Expected, claude.HasRateLimits(), tc.Case)
}
}

func TestClaudeFormattedCost(t *testing.T) {
cases := []struct {
Case string
Expand Down
20 changes: 20 additions & 0 deletions website/docs/segments/cli/claude.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ import Config from "@site/src/components/Config.js";
| `.TokenUsagePercent` | `Percentage` | Percentage of context window used (0-100) |
| `.FormattedCost` | `string` | Formatted cost string (e.g., "$0.15" or "$0.0012") |
| `.FormattedTokens` | `string` | Human-readable token count (e.g., "1.2K", "15.3M") |
| `.RateLimits` | `RateLimits` | Claude.ai subscription rate limit data (optional) |
| `.FiveHourPercent` | `Percentage` | 5-hour window usage percentage (0 if unavailable) |
| `.SevenDayPercent` | `Percentage` | 7-day window usage percentage (0 if unavailable) |
| `.HasRateLimits` | `bool` | Whether rate limit data is available |
| `.HasFiveHourLimit` | `bool` | Whether the 5-hour rate limit window is available |
| `.HasSevenDayLimit` | `bool` | Whether the 7-day rate limit window is available |

#### Model Properties

Expand Down Expand Up @@ -89,6 +95,20 @@ import Config from "@site/src/components/Config.js";
| `.InputTokens` | `int` | Input tokens for the current message |
| `.OutputTokens` | `int` | Output tokens for the current message |

#### RateLimits Properties

| Name | Type | Description |
| ----------- | ------------ | ------------------------------------------------ |
| `.FiveHour` | `RateWindow` | 5-hour session limit (nil when not available) |
| `.SevenDay` | `RateWindow` | 7-day weekly limit (nil when not available) |

#### RateWindow Properties

| Name | Type | Description |
| ----------------- | --------- | ---------------------------------------------- |
| `.UsedPercentage` | `float64` | Percentage of limit used (0-100) |
| `.ResetsAt` | `int64` | Unix epoch seconds when this window resets |

### Percentage Methods

The `TokenUsagePercent` property is a `Percentage` type that provides additional functionality:
Expand Down
Loading