package fetcher import ( "encoding/json" "os" "runtime" "time" ) // CacheData represents the JSON structure written to the cache file. // It mirrors the raw API response (five_hour, seven_day) plus error fields. type CacheData struct { FiveHour *UsageWindow `json:"five_hour,omitempty"` SevenDay *UsageWindow `json:"seven_day,omitempty"` Error string `json:"_error,omitempty"` Status int `json:"_status,omitempty"` Message string `json:"_message,omitempty"` } // UsageWindow represents a single usage window from the API. type UsageWindow struct { Utilization float64 `json:"utilization"` ResetsAt string `json:"resets_at"` } // CachePath returns the cache file path. func CachePath() string { if v := os.Getenv("CLAUDE_USAGE_CACHE"); v != "" { return v } if runtime.GOOS == "windows" { if tmp := os.Getenv("TEMP"); tmp != "" { return tmp + `\claude_usage.json` } return os.TempDir() + `\claude_usage.json` } return "/tmp/claude_usage.json" } // WriteCache writes data to the cache file as JSON. func WriteCache(data *CacheData) error { b, err := json.MarshalIndent(data, "", " ") if err != nil { return err } return os.WriteFile(CachePath(), b, 0o644) } // ReadCache reads and parses the cache file. Returns data and file age. // Returns nil if file doesn't exist or can't be parsed. func ReadCache() (*CacheData, time.Duration) { path := CachePath() info, err := os.Stat(path) if err != nil { return nil, 0 } age := time.Since(info.ModTime()) raw, err := os.ReadFile(path) if err != nil { return nil, 0 } var data CacheData if err := json.Unmarshal(raw, &data); err != nil { return nil, 0 } return &data, age } // ReadCacheIfFresh reads cache only if it's younger than maxAge. // Error caches are always returned regardless of age. func ReadCacheIfFresh(maxAge time.Duration) *CacheData { data, age := ReadCache() if data == nil { return nil } if data.Error != "" { return data } if age > maxAge { return nil } return data }