Compare commits

..

2 Commits

Author SHA1 Message Date
99ad5b9d7f Format markdown and YAML files with deno fmt 2025-12-18 19:42:05 +01:00
0638707349 Use golang.org/x/term for terminal width detection
Replace direct unix.IoctlGetWinsize() call with term.GetSize() for
cleaner API. No binary size change as x/sys remains an indirect
dependency.
2025-12-18 18:36:54 +01:00
6 changed files with 39 additions and 30 deletions

View File

@ -3,19 +3,26 @@
- Go 1.25; modules in `go.mod`; binary name `statusline`. - Go 1.25; modules in `go.mod`; binary name `statusline`.
- Build: `task build` (stripped) or `go build -o bin/statusline .`. - Build: `task build` (stripped) or `go build -o bin/statusline .`.
- Run fixture: `task run` or `cat test/fixture.json | ./bin/statusline`. - Run fixture: `task run` or `cat test/fixture.json | ./bin/statusline`.
- Tests: `task test` (`go test -v ./...`); single test: `go test -v -run 'TestName' ./...`. - Tests: `task test` (`go test -v ./...`); single test:
`go test -v -run 'TestName' ./...`.
- Coverage: `task test:cover`; benchmarks: `task bench` or `task bench:go`. - Coverage: `task test:cover`; benchmarks: `task bench` or `task bench:go`.
- Lint: `task lint` (`golangci-lint run`); auto-fix: `task lint:fix`. - Lint: `task lint` (`golangci-lint run`); auto-fix: `task lint:fix`.
- Formatting: `gofumpt` + `goimports` via golangci-lint; keep `go fmt`/gofumpt style. - Formatting: `gofumpt` + `goimports` via golangci-lint; keep `go fmt`/gofumpt
- Imports: organize with `goimports`; stdlib first, then third-party, then local. style.
- Imports: organize with `goimports`; stdlib first, then third-party, then
local.
- Types: prefer explicit types; avoid unused code (lint-enforced). - Types: prefer explicit types; avoid unused code (lint-enforced).
- Naming: follow Go conventions (ExportedCamelCase for exported, lowerCamelCase for unexported); no stutter. - Naming: follow Go conventions (ExportedCamelCase for exported, lowerCamelCase
- Errors: check and return errors; wrap or format with context; no silent ignores. for unexported); no stutter.
- Errors: check and return errors; wrap or format with context; no silent
ignores.
- Security: `gosec` enabled; avoid leaking secrets; handle paths carefully. - Security: `gosec` enabled; avoid leaking secrets; handle paths carefully.
- Tests should avoid external deps; skip when environment-dependent. - Tests should avoid external deps; skip when environment-dependent.
- Modernization helpers: `task modernize` / `task modernize:test` (gopls). - Modernization helpers: `task modernize` / `task modernize:test` (gopls).
- Clean artifacts: `task clean`; binary lives in `bin/`. - Clean artifacts: `task clean`; binary lives in `bin/`.
- Git info and gitea checks rely on `go-git` and `gopsutil`; keep deps updated via `go mod tidy`. - Git info and gitea checks rely on `go-git` and `gopsutil`; keep deps updated
- Keep ANSI handling via `stripANSI` regex in `main.go`; adjust carefully if changing. via `go mod tidy`.
- Keep ANSI handling via `stripANSI` regex in `main.go`; adjust carefully if
changing.
- No Cursor/Copilot rules present as of this file. - No Cursor/Copilot rules present as of this file.
- No emojis in code or docs unless explicitly requested. - No emojis in code or docs unless explicitly requested.

View File

@ -1,10 +1,14 @@
# CLAUDE.md # CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. This file provides guidance to Claude Code (claude.ai/code) when working with
code in this repository.
## Project Overview ## Project Overview
This is a custom status line binary for Claude Code, written in Go. It replaces shell-based status line scripts with a compiled binary for better performance. The binary reads JSON from stdin (provided by Claude Code) and outputs a formatted status line with ANSI colors. This is a custom status line binary for Claude Code, written in Go. It replaces
shell-based status line scripts with a compiled binary for better performance.
The binary reads JSON from stdin (provided by Claude Code) and outputs a
formatted status line with ANSI colors.
## Build and Development Commands ## Build and Development Commands
@ -35,7 +39,8 @@ task clean # Remove bin/ directory
Single-file Go application (`main.go`) that: Single-file Go application (`main.go`) that:
1. **Reads JSON from stdin** - Parses Claude Code's status hook payload (`StatusInput` struct) 1. **Reads JSON from stdin** - Parses Claude Code's status hook payload
(`StatusInput` struct)
2. **Gathers system state**: 2. **Gathers system state**:
- Gitea process status via gopsutil (cross-platform process listing) - Gitea process status via gopsutil (cross-platform process listing)
- Git repository info via go-git (branch name, dirty state) - Git repository info via go-git (branch name, dirty state)
@ -43,6 +48,7 @@ Single-file Go application (`main.go`) that:
3. **Outputs formatted status line** with ANSI colors, padding to terminal width 3. **Outputs formatted status line** with ANSI colors, padding to terminal width
Key functions: Key functions:
- `formatContextInfo()` - Formats token usage as "Xk/Yk" - `formatContextInfo()` - Formats token usage as "Xk/Yk"
- `getGiteaStatus()` - Returns green/red dot based on gitea process running - `getGiteaStatus()` - Returns green/red dot based on gitea process running
- `getGitInfo()` - Returns git branch and dirty indicator - `getGitInfo()` - Returns git branch and dirty indicator
@ -50,7 +56,9 @@ Key functions:
## JSON Input Format ## JSON Input Format
The binary expects Claude Code's status hook JSON via stdin. See `test/fixture.json` for the complete structure. Key fields used: The binary expects Claude Code's status hook JSON via stdin. See
`test/fixture.json` for the complete structure. Key fields used:
- `model.display_name` - Model name to display - `model.display_name` - Model name to display
- `workspace.current_dir` - Current directory path - `workspace.current_dir` - Current directory path
- `context_window.context_window_size` - Total context window tokens - `context_window.context_window_size` - Total context window tokens

View File

@ -1,6 +1,6 @@
# https://taskfile.dev # https://taskfile.dev
version: '3' version: "3"
vars: vars:
BINARY: statusline BINARY: statusline
@ -59,11 +59,11 @@ tasks:
deps: [build] deps: [build]
cmds: cmds:
- | - |
echo "=== Pure Go (100 runs) ===" echo "=== Pure Go (100 runs) ==="
time for i in $(seq 1 100); do cat test/fixture.json | ./bin/{{.BINARY}} >/dev/null; done time for i in $(seq 1 100); do cat test/fixture.json | ./bin/{{.BINARY}} >/dev/null; done
# echo "" # echo ""
# echo "=== Shell (100 runs) ===" # echo "=== Shell (100 runs) ==="
# time for i in $(seq 1 100); do cat test/fixture.json | ./statusline.sh >/dev/null; done # time for i in $(seq 1 100); do cat test/fixture.json | ./statusline.sh >/dev/null; done
silent: false silent: false
bench:go: bench:go:
@ -71,8 +71,8 @@ tasks:
deps: [build] deps: [build]
cmds: cmds:
- | - |
echo "=== Pure Go (100 runs) ===" echo "=== Pure Go (100 runs) ==="
time for i in $(seq 1 100); do cat test/fixture.json | ./bin/{{.BINARY}} >/dev/null; done time for i in $(seq 1 100); do cat test/fixture.json | ./bin/{{.BINARY}} >/dev/null; done
tidy: tidy:
desc: Run go mod tidy desc: Run go mod tidy

3
go.mod
View File

@ -5,7 +5,7 @@ go 1.25.5
require ( require (
github.com/go-git/go-git/v6 v6.0.0-20251216093047-22c365fcee9c github.com/go-git/go-git/v6 v6.0.0-20251216093047-22c365fcee9c
github.com/shirou/gopsutil/v4 v4.25.11 github.com/shirou/gopsutil/v4 v4.25.11
golang.org/x/sys v0.39.0 golang.org/x/term v0.38.0
) )
require ( require (
@ -30,4 +30,5 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/crypto v0.46.0 // indirect golang.org/x/crypto v0.46.0 // indirect
golang.org/x/net v0.48.0 // indirect golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
) )

7
go.sum
View File

@ -23,21 +23,17 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo= github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs= github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
github.com/go-git/go-billy/v6 v6.0.0-20251209065551-8afc3eb64e4d h1:nfZPVEha54DwXl8twSNxi9J8edIiqfpSvnq/mGPfgc4=
github.com/go-git/go-billy/v6 v6.0.0-20251209065551-8afc3eb64e4d/go.mod h1:d3XQcsHu1idnquxt48kAv+h+1MUiYKLH/e7LAzjP+pI=
github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd h1:Gd/f9cGi/3h1JOPaa6er+CkKUGyGX2DBJdFbDKVO+R0= github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd h1:Gd/f9cGi/3h1JOPaa6er+CkKUGyGX2DBJdFbDKVO+R0=
github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd/go.mod h1:d3XQcsHu1idnquxt48kAv+h+1MUiYKLH/e7LAzjP+pI= github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd/go.mod h1:d3XQcsHu1idnquxt48kAv+h+1MUiYKLH/e7LAzjP+pI=
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251205091929-ed656e84d025 h1:24Uc4y1yxMe8V30NhshaDdCaTOw97BWVhVGH/m1+udM= github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251205091929-ed656e84d025 h1:24Uc4y1yxMe8V30NhshaDdCaTOw97BWVhVGH/m1+udM=
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251205091929-ed656e84d025/go.mod h1:T6lRF5ejdxaYZLVaCTuTG1+ZSvwI/c2oeiTgBWORJ8Q= github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251205091929-ed656e84d025/go.mod h1:T6lRF5ejdxaYZLVaCTuTG1+ZSvwI/c2oeiTgBWORJ8Q=
github.com/go-git/go-git/v6 v6.0.0-20251216093047-22c365fcee9c h1:pR4UmnVFMjNw956fgu+JlSAvmx37qW4ttVF0cu7DL/Q= github.com/go-git/go-git/v6 v6.0.0-20251216093047-22c365fcee9c h1:pR4UmnVFMjNw956fgu+JlSAvmx37qW4ttVF0cu7DL/Q=
github.com/go-git/go-git/v6 v6.0.0-20251216093047-22c365fcee9c/go.mod h1:EPzgAjDnw+TaCt1w/JUmj+SXwWHUae3c078ixiZQ10Y= github.com/go-git/go-git/v6 v6.0.0-20251216093047-22c365fcee9c/go.mod h1:EPzgAjDnw+TaCt1w/JUmj+SXwWHUae3c078ixiZQ10Y=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
@ -47,8 +43,6 @@ github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k= github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
@ -84,7 +78,6 @@ golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -11,7 +11,7 @@ import (
"github.com/go-git/go-git/v6" "github.com/go-git/go-git/v6"
"github.com/shirou/gopsutil/v4/process" "github.com/shirou/gopsutil/v4/process"
"golang.org/x/sys/unix" "golang.org/x/term"
) )
const statuslineWidthOffset = 7 const statuslineWidthOffset = 7
@ -197,11 +197,11 @@ func getGitInfo(cwd string) string {
} }
func getTerminalWidth() int { func getTerminalWidth() int {
ws, err := unix.IoctlGetWinsize(int(os.Stdout.Fd()), unix.TIOCGWINSZ) width, _, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil { if err != nil {
return 80 return 80
} }
return int(ws.Col) return width
} }
var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*m`) var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*m`)