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/term" ) const statuslineWidthOffset = 7 // TokenUsage tracks context window token consumption. type TokenUsage struct { InputTokens int `json:"input_tokens"` CacheCreationTokens int `json:"cache_creation_input_tokens"` CacheReadInputTokens int `json:"cache_read_input_tokens"` } // StatusInput represents the JSON input 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 *TokenUsage `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" ) // readInputFromStdin reads JSON input from stdin. func readInputFromStdin(r *bufio.Reader) string { var input strings.Builder for { line, err := r.ReadString('\n') input.WriteString(line) if err != nil { break } } return input.String() } // parseStatusInput unmarshals JSON string into StatusInput. func parseStatusInput(jsonStr string) (*StatusInput, error) { var data StatusInput if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { return nil, err } return &data, nil } // buildStatusLine constructs the left and right parts of the status line. func buildStatusLine(data *StatusInput) (left, right string) { contextInfo := formatContextInfo(data.ContextWindow.ContextWindowSize, data.ContextWindow.CurrentUsage) dirName := filepath.Base(data.Workspace.CurrentDir) giteaStatus := getGiteaStatus() gitInfo := getGitInfo(data.Workspace.CurrentDir) 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) right = fmt.Sprintf("%s%s%s", yellow, contextInfo, reset) return left, right } // calculatePadding returns the number of spaces needed for padding. func calculatePadding(leftVisible, rightVisible string, termWidth int) int { return max(termWidth-len(leftVisible)-len(rightVisible), 1) } // formatOutput combines left, right, and padding into final output. func formatOutput(left, right string, padding int) string { return fmt.Sprintf("%s%s%s", left, strings.Repeat(" ", padding), right) } func main() { jsonStr := readInputFromStdin(bufio.NewReader(os.Stdin)) data, err := parseStatusInput(jsonStr) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing JSON: %v\n", err) os.Exit(1) } left, right := buildStatusLine(data) // Calculate visible lengths (strip ANSI) leftVisible := stripANSI(left) rightVisible := stripANSI(right) // Get terminal width termWidth := getTerminalWidth() - statuslineWidthOffset // Calculate and apply padding padding := calculatePadding(leftVisible, rightVisible, termWidth) output := formatOutput(left, right, padding) fmt.Print(output) } func formatContextInfo(contextSize int, usage *TokenUsage) 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 { width, _, err := term.GetSize(int(os.Stdout.Fd())) if err != nil { return 80 } return width } var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*m`) func stripANSI(s string) string { return ansiRegex.ReplaceAllString(s, "") }