Rewrite in Go: static binaries, zero runtime dependencies
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:
Axel Meyer
2026-02-26 15:27:10 +00:00
parent 59afabd65a
commit 7f17a40b7c
33 changed files with 1275 additions and 1512 deletions

View File

@@ -0,0 +1,125 @@
package renderer
import (
"bytes"
"image"
"image/color"
"image/png"
"math"
"github.com/fogleman/gg"
)
const iconSize = 256
// Claude orange for the starburst logo.
var claudeOrange = color.RGBA{224, 123, 83, 255}
// arcColors maps usage percentage thresholds to colors.
var arcColors = []struct {
threshold int
color color.RGBA
}{
{10, color.RGBA{76, 175, 80, 255}}, // green
{20, color.RGBA{67, 160, 71, 255}}, // darker green
{30, color.RGBA{124, 179, 66, 255}}, // light green
{40, color.RGBA{192, 202, 51, 255}}, // lime
{50, color.RGBA{253, 216, 53, 255}}, // yellow
{60, color.RGBA{255, 193, 7, 255}}, // amber
{70, color.RGBA{255, 179, 0, 255}}, // darker amber
{80, color.RGBA{255, 152, 0, 255}}, // orange
{90, color.RGBA{255, 87, 34, 255}}, // deep orange
{100, color.RGBA{244, 67, 54, 255}}, // red
}
func getArcColor(pct int) color.RGBA {
for _, ac := range arcColors {
if pct <= ac.threshold {
return ac.color
}
}
return arcColors[len(arcColors)-1].color
}
// drawStarburst draws the 8-petal Claude logo.
func drawStarburst(dc *gg.Context) {
cx := float64(iconSize) / 2
cy := float64(iconSize) / 2
petalLen := float64(iconSize) * 0.38
petalWidth := float64(iconSize) * 0.10
centerRadius := float64(iconSize) * 0.04
dc.SetColor(claudeOrange)
for i := 0; i < 8; i++ {
angle := float64(i) * (2 * math.Pi / 8)
// Tip of the petal
tipX := cx + petalLen*math.Cos(angle)
tipY := cy + petalLen*math.Sin(angle)
// Base points (perpendicular to angle)
perpAngle := angle + math.Pi/2
baseX1 := cx + petalWidth*math.Cos(perpAngle)
baseY1 := cy + petalWidth*math.Sin(perpAngle)
baseX2 := cx - petalWidth*math.Cos(perpAngle)
baseY2 := cy - petalWidth*math.Sin(perpAngle)
// Inner point (slightly behind center for petal shape)
innerX := cx - petalWidth*0.5*math.Cos(angle)
innerY := cy - petalWidth*0.5*math.Sin(angle)
dc.MoveTo(innerX, innerY)
dc.LineTo(baseX1, baseY1)
dc.LineTo(tipX, tipY)
dc.LineTo(baseX2, baseY2)
dc.ClosePath()
dc.Fill()
}
// Center dot
dc.DrawCircle(cx, cy, centerRadius)
dc.Fill()
}
// drawArc draws a circular progress arc.
func drawArc(dc *gg.Context, pct int) {
if pct <= 0 {
return
}
if pct > 100 {
pct = 100
}
cx := float64(iconSize) / 2
cy := float64(iconSize) / 2
radius := float64(iconSize)/2 - 14 // inset from edge
arcWidth := 28.0
startAngle := -math.Pi / 2 // 12 o'clock
endAngle := startAngle + (float64(pct)/100)*2*math.Pi
dc.SetColor(getArcColor(pct))
dc.SetLineWidth(arcWidth)
dc.SetLineCap(gg.LineCapButt)
dc.DrawArc(cx, cy, radius, startAngle, endAngle)
dc.Stroke()
}
// RenderIcon generates a 256x256 PNG icon with starburst and usage arc.
func RenderIcon(pct int) image.Image {
dc := gg.NewContext(iconSize, iconSize)
drawStarburst(dc)
drawArc(dc, pct)
return dc.Image()
}
// RenderIconPNG generates the icon as PNG bytes (for systray).
func RenderIconPNG(pct int) ([]byte, error) {
img := RenderIcon(pct)
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
return nil, err
}
return buf.Bytes(), nil
}