Remove standalone fetcher, add setup tool with install/uninstall workflow
All checks were successful
Release / build (push) Successful in 1m45s

Drop claude-fetcher binary and cron job — the widget's built-in
BackgroundFetcher is the sole fetcher now. Add cmd/setup with cross-platform
install and uninstall (--uninstall): kills widget, removes binaries + autostart,
cleans Claude Code statusline setting, optionally removes config dir.

Also includes: browser-based login (chromedp), ICO wrapper for Windows tray
icon, and reduced icon size (64px).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Axel Meyer
2026-02-26 19:11:08 +01:00
parent 5b0366f16b
commit 47165ce02c
15 changed files with 796 additions and 126 deletions

View File

@@ -2,15 +2,17 @@ package renderer
import (
"bytes"
"encoding/binary"
"image"
"image/color"
"image/png"
"math"
"runtime"
"github.com/fogleman/gg"
)
const iconSize = 256
const iconSize = 64
// Claude orange for the starburst logo.
var claudeOrange = color.RGBA{224, 123, 83, 255}
@@ -93,8 +95,8 @@ func drawArc(dc *gg.Context, pct int) {
cx := float64(iconSize) / 2
cy := float64(iconSize) / 2
radius := float64(iconSize)/2 - 14 // inset from edge
arcWidth := 28.0
radius := float64(iconSize)/2 - 4 // inset from edge
arcWidth := 7.0
startAngle := -math.Pi / 2 // 12 o'clock
endAngle := startAngle + (float64(pct)/100)*2*math.Pi
@@ -114,7 +116,7 @@ func RenderIcon(pct int) image.Image {
return dc.Image()
}
// RenderIconPNG generates the icon as PNG bytes (for systray).
// RenderIconPNG generates the icon as PNG bytes.
func RenderIconPNG(pct int) ([]byte, error) {
img := RenderIcon(pct)
var buf bytes.Buffer
@@ -123,3 +125,54 @@ func RenderIconPNG(pct int) ([]byte, error) {
}
return buf.Bytes(), nil
}
// RenderIconForTray returns icon bytes suitable for systray.SetIcon:
// ICO (PNG-compressed) on Windows, raw PNG on other platforms.
func RenderIconForTray(pct int) ([]byte, error) {
pngData, err := RenderIconPNG(pct)
if err != nil {
return nil, err
}
if runtime.GOOS != "windows" {
return pngData, nil
}
return wrapPNGInICO(pngData, iconSize, iconSize), nil
}
// wrapPNGInICO wraps raw PNG bytes in a minimal ICO container.
// Windows Vista+ supports PNG-compressed ICO entries.
func wrapPNGInICO(pngData []byte, width, height int) []byte {
const headerSize = 6
const entrySize = 16
imageOffset := headerSize + entrySize
buf := new(bytes.Buffer)
// ICONDIR header
binary.Write(buf, binary.LittleEndian, uint16(0)) // Reserved
binary.Write(buf, binary.LittleEndian, uint16(1)) // Type: 1 = icon
binary.Write(buf, binary.LittleEndian, uint16(1)) // Count: 1 image
// ICONDIRENTRY
w := byte(width)
if width >= 256 {
w = 0 // 0 means 256
}
h := byte(height)
if height >= 256 {
h = 0
}
buf.WriteByte(w) // Width
buf.WriteByte(h) // Height
buf.WriteByte(0) // ColorCount (0 = no palette)
buf.WriteByte(0) // Reserved
binary.Write(buf, binary.LittleEndian, uint16(1)) // Planes
binary.Write(buf, binary.LittleEndian, uint16(32)) // BitCount
binary.Write(buf, binary.LittleEndian, uint32(len(pngData))) // BytesInRes
binary.Write(buf, binary.LittleEndian, uint32(imageOffset)) // ImageOffset
// PNG image data
buf.Write(pngData)
return buf.Bytes()
}