Some checks failed
Release / build (push) Failing after 21s
Replace Node.js + Python codebase with three Go binaries: - claude-statusline: CLI status bar for Claude Code - claude-fetcher: standalone cron job for API usage - claude-widget: system tray icon (fyne-io/systray + fogleman/gg) All CGO-free for trivial cross-compilation. Add nfpm .deb packaging with autostart and cron. CI pipeline produces Linux + Windows binaries, .deb, .tar.gz, and .zip release assets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
86 lines
2.0 KiB
Go
86 lines
2.0 KiB
Go
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
|
|
}
|