- Add .golangci.yml with sensible defaults for the project - Add lint and lint:fix tasks to Taskfile - Remove unused findGitRoot function - Apply gofumpt formatting fixes
194 lines
4.3 KiB
Go
194 lines
4.3 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/go-git/go-git/v6"
|
|
"github.com/shirou/gopsutil/v4/process"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
const statuslineWidthOffset = 7
|
|
|
|
// Input JSON structure from Claude Code
|
|
type StatusInput struct {
|
|
Model struct {
|
|
DisplayName string `json:"display_name"`
|
|
} `json:"model"`
|
|
Workspace struct {
|
|
CurrentDir string `json:"current_dir"`
|
|
} `json:"workspace"`
|
|
ContextWindow struct {
|
|
ContextWindowSize int `json:"context_window_size"`
|
|
CurrentUsage *struct {
|
|
InputTokens int `json:"input_tokens"`
|
|
CacheCreationTokens int `json:"cache_creation_input_tokens"`
|
|
CacheReadInputTokens int `json:"cache_read_input_tokens"`
|
|
} `json:"current_usage"`
|
|
} `json:"context_window"`
|
|
}
|
|
|
|
// ANSI color codes
|
|
const (
|
|
reset = "\033[0m"
|
|
bold = "\033[1m"
|
|
red = "\033[31m"
|
|
green = "\033[32m"
|
|
yellow = "\033[33m"
|
|
magenta = "\033[35m"
|
|
cyan = "\033[36m"
|
|
boldGreen = "\033[1;32m"
|
|
)
|
|
|
|
func main() {
|
|
// Read JSON from stdin
|
|
reader := bufio.NewReader(os.Stdin)
|
|
var input strings.Builder
|
|
for {
|
|
line, err := reader.ReadString('\n')
|
|
input.WriteString(line)
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
var data StatusInput
|
|
if err := json.Unmarshal([]byte(input.String()), &data); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error parsing JSON: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Calculate context info
|
|
contextInfo := formatContextInfo(data.ContextWindow.ContextWindowSize, data.ContextWindow.CurrentUsage)
|
|
|
|
// Get directory name
|
|
dirName := filepath.Base(data.Workspace.CurrentDir)
|
|
|
|
// Check gitea status
|
|
giteaStatus := getGiteaStatus()
|
|
|
|
// Get git info
|
|
gitInfo := getGitInfo(data.Workspace.CurrentDir)
|
|
|
|
// Build left part
|
|
left := fmt.Sprintf("%s %s%s%s %s➜%s %s%s%s%s",
|
|
giteaStatus,
|
|
magenta, data.Model.DisplayName, reset,
|
|
boldGreen, reset,
|
|
cyan, dirName, reset,
|
|
gitInfo)
|
|
|
|
// Build right part
|
|
right := fmt.Sprintf("%s%s%s", yellow, contextInfo, reset)
|
|
|
|
// Calculate visible lengths (strip ANSI)
|
|
leftVisible := stripANSI(left)
|
|
rightVisible := stripANSI(right)
|
|
|
|
// Get terminal width
|
|
termWidth := getTerminalWidth() - statuslineWidthOffset
|
|
|
|
// Calculate padding
|
|
padding := max(termWidth-len(leftVisible)-len(rightVisible), 1)
|
|
|
|
// Output with padding
|
|
fmt.Printf("%s%s%s", left, strings.Repeat(" ", padding), right)
|
|
}
|
|
|
|
func formatContextInfo(contextSize int, usage *struct {
|
|
InputTokens int `json:"input_tokens"`
|
|
CacheCreationTokens int `json:"cache_creation_input_tokens"`
|
|
CacheReadInputTokens int `json:"cache_read_input_tokens"`
|
|
},
|
|
) string {
|
|
totalK := contextSize / 1000
|
|
|
|
if usage == nil {
|
|
return fmt.Sprintf("0/%dk", totalK)
|
|
}
|
|
|
|
currentTokens := usage.InputTokens + usage.CacheCreationTokens + usage.CacheReadInputTokens
|
|
currentK := currentTokens / 1000
|
|
return fmt.Sprintf("%dk/%dk", currentK, totalK)
|
|
}
|
|
|
|
func getGiteaStatus() string {
|
|
// Check if gitea process is running using gopsutil (cross-platform)
|
|
procs, err := process.Processes()
|
|
if err != nil {
|
|
return red + "●" + reset
|
|
}
|
|
|
|
for _, p := range procs {
|
|
name, err := p.Name()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if name == "gitea" {
|
|
return green + "●" + reset
|
|
}
|
|
}
|
|
return red + "●" + reset
|
|
}
|
|
|
|
func getGitInfo(cwd string) string {
|
|
// Open the repository (searches up from cwd)
|
|
repo, err := git.PlainOpenWithOptions(cwd, &git.PlainOpenOptions{
|
|
DetectDotGit: true,
|
|
})
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
// Get HEAD reference
|
|
head, err := repo.Head()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
// Get branch name
|
|
var branch string
|
|
if head.Name().IsBranch() {
|
|
branch = head.Name().Short()
|
|
} else {
|
|
// Detached HEAD - use short hash
|
|
branch = head.Hash().String()[:7]
|
|
}
|
|
|
|
// Check if working tree is dirty
|
|
worktree, err := repo.Worktree()
|
|
if err != nil {
|
|
return fmt.Sprintf(" git:(%s)", branch)
|
|
}
|
|
|
|
status, err := worktree.Status()
|
|
if err != nil {
|
|
return fmt.Sprintf(" git:(%s)", branch)
|
|
}
|
|
|
|
if !status.IsClean() {
|
|
return fmt.Sprintf(" git:(%s) ✗", branch)
|
|
}
|
|
return fmt.Sprintf(" git:(%s)", branch)
|
|
}
|
|
|
|
func getTerminalWidth() int {
|
|
ws, err := unix.IoctlGetWinsize(int(os.Stdout.Fd()), unix.TIOCGWINSZ)
|
|
if err != nil {
|
|
return 80
|
|
}
|
|
return int(ws.Col)
|
|
}
|
|
|
|
var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*m`)
|
|
|
|
func stripANSI(s string) string {
|
|
return ansiRegex.ReplaceAllString(s, "")
|
|
}
|