Rewrite in Go: static binaries, zero runtime dependencies
Some checks failed
Release / build (push) Failing after 21s
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>
This commit is contained in:
120
cmd/statusline/main.go
Normal file
120
cmd/statusline/main.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.davoryn.de/calic/claude-statusline/internal/fetcher"
|
||||
)
|
||||
|
||||
const (
|
||||
filledBlock = "\u2593"
|
||||
lightBlock = "\u2591"
|
||||
)
|
||||
|
||||
// stdinData is the JSON structure from Claude Code's stdin.
|
||||
type stdinData struct {
|
||||
ContextWindow struct {
|
||||
UsedPercentage float64 `json:"used_percentage"`
|
||||
} `json:"context_window"`
|
||||
}
|
||||
|
||||
func bar(pct float64, width int) string {
|
||||
pct = math.Max(0, math.Min(100, pct))
|
||||
filled := int(math.Floor(pct / (100.0 / float64(width))))
|
||||
return strings.Repeat(filledBlock, filled) + strings.Repeat(lightBlock, width-filled)
|
||||
}
|
||||
|
||||
func getContextPart(data *stdinData) string {
|
||||
if data == nil {
|
||||
return ""
|
||||
}
|
||||
pct := data.ContextWindow.UsedPercentage
|
||||
return fmt.Sprintf("Context %s %d%%", bar(pct, 10), int(pct))
|
||||
}
|
||||
|
||||
func formatMinutes(isoStr string) string {
|
||||
if isoStr == "" {
|
||||
return ""
|
||||
}
|
||||
t, err := time.Parse(time.RFC3339, isoStr)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
mins := int(math.Max(0, time.Until(t).Minutes()))
|
||||
return fmt.Sprintf("%dM", mins)
|
||||
}
|
||||
|
||||
func getUsagePart(data *fetcher.CacheData) string {
|
||||
if data == nil {
|
||||
return ""
|
||||
}
|
||||
if data.Error != "" {
|
||||
switch data.Error {
|
||||
case "auth_expired":
|
||||
return "Token: session expired"
|
||||
case "no_session_key":
|
||||
return ""
|
||||
default:
|
||||
return "Token: error"
|
||||
}
|
||||
}
|
||||
|
||||
var parts []string
|
||||
|
||||
if data.FiveHour != nil && data.FiveHour.Utilization > 0 {
|
||||
pct := data.FiveHour.Utilization
|
||||
s := fmt.Sprintf("Token %s %d%%", bar(pct, 10), int(math.Round(pct)))
|
||||
if mins := formatMinutes(data.FiveHour.ResetsAt); mins != "" {
|
||||
s += " " + mins
|
||||
}
|
||||
parts = append(parts, s)
|
||||
}
|
||||
|
||||
if data.SevenDay != nil && data.SevenDay.Utilization > 20 {
|
||||
parts = append(parts, fmt.Sprintf("7d %d%%", int(math.Round(data.SevenDay.Utilization))))
|
||||
}
|
||||
|
||||
return strings.Join(parts, " | ")
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Read stdin (context window data from Claude Code)
|
||||
var input stdinData
|
||||
var hasInput bool
|
||||
dec := json.NewDecoder(os.Stdin)
|
||||
if err := dec.Decode(&input); err == nil {
|
||||
hasInput = true
|
||||
}
|
||||
|
||||
// Read cache
|
||||
maxAge := 900 * time.Second
|
||||
if v := os.Getenv("CLAUDE_USAGE_MAX_AGE"); v != "" {
|
||||
if secs, err := strconv.Atoi(v); err == nil {
|
||||
maxAge = time.Duration(secs) * time.Second
|
||||
}
|
||||
}
|
||||
cache := fetcher.ReadCacheIfFresh(maxAge)
|
||||
|
||||
// Build output
|
||||
var parts []string
|
||||
|
||||
if hasInput {
|
||||
if ctx := getContextPart(&input); ctx != "" {
|
||||
parts = append(parts, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if usage := getUsagePart(cache); usage != "" {
|
||||
parts = append(parts, usage)
|
||||
}
|
||||
|
||||
if len(parts) > 0 {
|
||||
fmt.Println(strings.Join(parts, " | "))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user