diff --git a/src/segments/claude.go b/src/segments/claude.go index 9999cf2e431e..4d3600129c5e 100644 --- a/src/segments/claude.go +++ b/src/segments/claude.go @@ -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 @@ -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 @@ -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 + } + + 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 + } + + return text.Percentage(percent) +} + +// HasRateLimits returns true when rate limit data is available. +func (c *Claude) HasRateLimits() bool { + return c.RateLimits != nil +} + +// 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 +} + +// 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 +} diff --git a/src/segments/claude_test.go b/src/segments/claude_test.go index 3e0981364510..7ebf7be4314d 100644 --- a/src/segments/claude_test.go +++ b/src/segments/claude_test.go @@ -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 diff --git a/website/docs/segments/cli/claude.mdx b/website/docs/segments/cli/claude.mdx index 9094ce17fd46..5e4f30935fa1 100644 --- a/website/docs/segments/cli/claude.mdx +++ b/website/docs/segments/cli/claude.mdx @@ -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 @@ -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: