diff --git a/.gitignore b/.gitignore index c7a534f..32aec9c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ /statusline /bin +/.task + # Created by https://gitignore.kjanat.com/api/go,linux # Edit at https://gitignore.kjanat.com?templates=go,linux diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..761d7e0 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,89 @@ +# https://taskfile.dev + +version: '3' + +vars: + BINARY: statusline + LDFLAGS: -s -w + +tasks: + default: + desc: Build the statusline binary + cmds: + - task: build + + build: + desc: Build with stripped symbols + cmds: + - go build -ldflags="{{.LDFLAGS}}" -o {{.BINARY}} . + sources: + - "*.go" + - go.mod + - go.sum + generates: + - "{{.BINARY}}" + + build:debug: + desc: Build with debug symbols + cmds: + - go build -o {{.BINARY}} . + + run: + desc: Run with test fixture + deps: [build] + cmds: + - cat test/fixture.json | ./{{.BINARY}} + + test: + desc: Run with test fixture and show output + deps: [build] + cmds: + - echo "=== Output ===" + - cat test/fixture.json | ./{{.BINARY}} + - echo "" + - echo "=== Timing (single run) ===" + - time sh -c 'cat test/fixture.json | ./{{.BINARY}} > /dev/null' + + bench: + desc: Benchmark Go vs Shell (100 runs) + deps: [build] + cmds: + - | + echo "=== Pure Go (100 runs) ===" + time for i in $(seq 1 100); do cat test/fixture.json | ./{{.BINARY}} >/dev/null; done + echo "" + echo "=== Shell (100 runs) ===" + time for i in $(seq 1 100); do cat test/fixture.json | ./statusline.sh >/dev/null; done + silent: false + + bench:go: + desc: Benchmark Go only (100 runs) + deps: [build] + cmds: + - | + echo "=== Pure Go (100 runs) ===" + time for i in $(seq 1 100); do cat test/fixture.json | ./{{.BINARY}} >/dev/null; done + + tidy: + desc: Run go mod tidy + cmds: + - go mod tidy + + clean: + desc: Remove built binary + cmds: + - rm -f {{.BINARY}} + + size: + desc: Show binary size + deps: [build] + cmds: + - ls -lh {{.BINARY}} + + install: + desc: Install to ~/.claude/ + deps: [build] + cmds: + - mkdir -p ~/.claude + - cp {{.BINARY}} ~/.claude/{{.BINARY}} + - echo "Installed to ~/.claude/{{.BINARY}}" diff --git a/go.mod b/go.mod index 87ba1fd..aed146f 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.24.11 require ( github.com/go-git/go-git/v6 v6.0.0-20251216093047-22c365fcee9c + github.com/shirou/gopsutil/v4 v4.25.11 golang.org/x/sys v0.39.0 ) @@ -12,14 +13,21 @@ require ( github.com/ProtonMail/go-crypto v1.3.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/ebitengine/purego v0.9.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/go-git/gcfg/v2 v2.0.2 // indirect github.com/go-git/go-billy/v6 v6.0.0-20251209065551-8afc3eb64e4d // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/kevinburke/ssh_config v1.4.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/sergi/go-diff v1.4.0 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/crypto v0.46.0 // indirect golang.org/x/net v0.48.0 // indirect ) diff --git a/go.sum b/go.sum index d0da2d7..f87b56b 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,8 @@ github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -27,8 +29,13 @@ github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251205091929-ed656e84d025 h1:24U 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/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/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/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/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= @@ -36,26 +43,41 @@ 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/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/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/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shirou/gopsutil/v4 v4.25.11 h1:X53gB7muL9Gnwwo2evPSE+SfOrltMoR6V3xJAXZILTY= +github.com/shirou/gopsutil/v4 v4.25.11/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 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/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= 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 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index 37d0225..b681dd4 100644 --- a/main.go +++ b/main.go @@ -5,12 +5,12 @@ import ( "encoding/json" "fmt" "os" - "os/exec" "path/filepath" "regexp" "strings" "github.com/go-git/go-git/v6" + "github.com/shirou/gopsutil/v4/process" "golang.org/x/sys/unix" ) @@ -118,10 +118,20 @@ func formatContextInfo(contextSize int, usage *struct { } func getGiteaStatus() string { - // Check if gitea process is running using pgrep - cmd := exec.Command("pgrep", "-x", "gitea") - if err := cmd.Run(); err == nil { - return green + "●" + reset + // 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 } diff --git a/test/fixture.json b/test/fixture.json new file mode 100644 index 0000000..4e4e815 --- /dev/null +++ b/test/fixture.json @@ -0,0 +1,36 @@ +{ + "hook_event_name": "Status", + "session_id": "abc123def456", + "transcript_path": "/tmp/transcript.json", + "cwd": "/root/projects/statusline", + "model": { + "id": "claude-opus-4-5-20251101", + "display_name": "Opus 4.5" + }, + "workspace": { + "current_dir": "/root/projects/statusline", + "project_dir": "/root/projects/statusline" + }, + "version": "1.0.80", + "output_style": { + "name": "default" + }, + "cost": { + "total_cost_usd": 0.01234, + "total_duration_ms": 45000, + "total_api_duration_ms": 2300, + "total_lines_added": 156, + "total_lines_removed": 23 + }, + "context_window": { + "total_input_tokens": 15234, + "total_output_tokens": 4521, + "context_window_size": 200000, + "current_usage": { + "input_tokens": 8500, + "output_tokens": 1200, + "cache_creation_input_tokens": 5000, + "cache_read_input_tokens": 2000 + } + } +}