claude_usage/lib.rs
1//! # claude-usage
2//!
3//! A library for fetching Claude API usage data from Anthropic.
4//!
5//! This crate provides a simple API to retrieve usage statistics including
6//! 5-hour and 7-day utilization percentages. It handles credential retrieval
7//! from platform-specific secure storage and returns typed, structured data.
8//!
9//! ## Quick Start
10//!
11//! ```rust,ignore
12//! use claude_usage::get_usage;
13//!
14//! let usage = get_usage()?;
15//! println!("5h utilization: {}%", usage.five_hour.utilization);
16//! println!("7d utilization: {}%", usage.seven_day.utilization);
17//! ```
18//!
19//! ## Features
20//!
21//! - **Cross-platform credentials**: macOS Keychain, Linux credential file
22//! - **Typed responses**: [`UsageData`], [`UsagePeriod`], [`ExtraUsage`]
23//! - **Secure handling**: Tokens are read, used, and immediately discarded
24//! - **Helper methods**: Check if usage is on-pace, time until reset
25//! - **Node.js bindings**: Available via the `napi` feature
26//!
27//! ## Platform Support
28//!
29//! | Platform | Credential Source | Status |
30//! |----------|-------------------|--------|
31//! | macOS | Keychain ("Claude Code-credentials") | ✅ |
32//! | Linux | `~/.claude/.credentials.json` | ✅ |
33//! | Windows | - | ❌ |
34//!
35//! ## Usage Examples
36//!
37//! ### Basic Usage
38//!
39//! ```rust,ignore
40//! use claude_usage::get_usage;
41//!
42//! fn main() -> Result<(), claude_usage::Error> {
43//! let usage = get_usage()?;
44//!
45//! println!("5-hour: {}%", usage.five_hour.utilization);
46//! println!("7-day: {}%", usage.seven_day.utilization);
47//!
48//! Ok(())
49//! }
50//! ```
51//!
52//! ### Checking If Usage Is Sustainable
53//!
54//! ```rust,ignore
55//! use claude_usage::get_usage;
56//!
57//! let usage = get_usage()?;
58//!
59//! // Check if current usage rate won't exceed quota before reset
60//! if usage.five_hour_on_pace() {
61//! println!("5-hour usage is sustainable");
62//! } else {
63//! println!("Warning: 5-hour usage may exceed quota!");
64//! }
65//!
66//! if usage.seven_day_on_pace() {
67//! println!("7-day usage is sustainable");
68//! }
69//! ```
70//!
71//! ### Time Until Reset
72//!
73//! ```rust,ignore
74//! use claude_usage::get_usage;
75//!
76//! let usage = get_usage()?;
77//!
78//! let time_left = usage.five_hour.time_until_reset();
79//! println!("5-hour quota resets in {} minutes", time_left.num_minutes());
80//!
81//! // Get percentage of period elapsed
82//! let elapsed = usage.five_hour.time_elapsed_percent(5);
83//! println!("{}% of 5-hour period has elapsed", elapsed);
84//! ```
85//!
86//! ### Handling Errors
87//!
88//! ```rust,ignore
89//! use claude_usage::{get_usage, Error, CredentialError, ApiError};
90//!
91//! match get_usage() {
92//! Ok(usage) => println!("Usage: {}%", usage.five_hour.utilization),
93//! Err(Error::Credential(CredentialError::NotFound)) => {
94//! eprintln!("Please run `claude` to login first");
95//! }
96//! Err(Error::Credential(CredentialError::Expired)) => {
97//! eprintln!("Token expired. Please run `claude` to re-login");
98//! }
99//! Err(Error::Api(ApiError::RateLimited { retry_after })) => {
100//! eprintln!("Rate limited. Retry after: {:?}", retry_after);
101//! }
102//! Err(e) => eprintln!("Error: {}", e),
103//! }
104//! ```
105//!
106//! ## Environment Variable
107//!
108//! The `CLAUDE_CODE_OAUTH_TOKEN` environment variable can override
109//! file-based credential retrieval on any platform:
110//!
111//! ```bash
112//! export CLAUDE_CODE_OAUTH_TOKEN="sk-ant-oat01-..."
113//! ```
114//!
115//! ## Module Overview
116//!
117//! - [`client`]: HTTP client for the Anthropic usage API
118//! - [`credentials`]: Platform-specific credential retrieval
119//! - [`types`]: Response types ([`UsageData`], [`UsagePeriod`], [`ExtraUsage`])
120//! - [`error`]: Error types ([`Error`], [`CredentialError`], [`ApiError`])
121//! - `napi`: Node.js bindings (requires `napi` feature)
122//!
123//! ## Security
124//!
125//! This crate follows strict security practices:
126//!
127//! 1. Tokens are read from secure storage, used once, and immediately discarded
128//! 2. Tokens are never stored in memory, logged, or passed to other modules
129//! 3. Error messages use generic text to prevent credential exposure
130
131pub mod client;
132pub mod credentials;
133pub mod error;
134#[cfg(feature = "napi")]
135pub mod napi;
136pub mod types;
137
138#[cfg(feature = "blocking")]
139pub use client::fetch_usage_raw;
140pub use credentials::get_token;
141pub use error::{ApiError, CredentialError, Error};
142pub use types::{ExtraUsage, UsageData, UsagePeriod};
143
144/// Fetch current Claude API usage data.
145///
146/// This is the main entry point for the crate. It:
147/// 1. Retrieves credentials from platform-specific storage
148/// 2. Calls the Anthropic usage API
149/// 3. Returns typed usage data
150///
151/// # Example
152///
153/// ```rust,ignore
154/// use claude_usage::get_usage;
155///
156/// let usage = get_usage()?;
157/// println!("5h utilization: {}%", usage.five_hour.utilization);
158/// println!("7d utilization: {}%", usage.seven_day.utilization);
159/// ```
160///
161/// # Errors
162///
163/// Returns [`Error`] if:
164/// - Credentials are not found or expired
165/// - API call fails
166/// - Response parsing fails
167#[cfg(feature = "blocking")]
168pub fn get_usage() -> Result<UsageData, Error> {
169 let token = credentials::get_token()?;
170 let response = client::fetch_usage_raw(&token)?;
171 let usage: UsageData =
172 serde_json::from_str(&response).map_err(|e| Error::Parse(e.to_string()))?;
173 Ok(usage)
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 #[ignore = "requires real credentials"]
182 #[cfg(feature = "blocking")]
183 fn test_get_usage_integration() {
184 let result = get_usage();
185 match result {
186 Ok(usage) => {
187 println!("5h utilization: {}%", usage.five_hour.utilization);
188 println!("7d utilization: {}%", usage.seven_day.utilization);
189 assert!(usage.five_hour.utilization >= 0.0);
190 assert!(usage.seven_day.utilization >= 0.0);
191 }
192 Err(e) => {
193 println!("Error: {}", e);
194 }
195 }
196 }
197}