Compare commits

...

10 Commits

Author SHA1 Message Date
32b48c7e5c Add extra spacing between git info and token usage 2026-02-09 16:46:47 +01:00
bca7b59248 Drop padding and terminal width detection
Output left + right with a single space separator instead of trying
to right-align to a detected terminal width. Terminal detection is
unreliable when all fds are piped by Claude Code, and hardcoding a
width defeats the purpose. Also removes the x/term dependency.
2026-02-08 23:37:48 +01:00
27128e2e3b Use STATUSLINE_WIDTH env var for reliable width detection
term.GetSize returns unreliable values when running inside Claude Code
over SSH. Replace the hardcoded statuslineWidthOffset with STATUSLINE_WIDTH
env var (set in Claude Code settings), falling back to terminal detection,
then 80 columns.
2026-02-08 23:27:26 +01:00
a06aa4c549 Fix right-alignment by detecting terminal width from stderr/stdin
When Claude Code captures stdout, term.GetSize on stdout fails and
falls back to 80 columns. Now tries stderr and stdin first (which
remain connected to the TTY), then COLUMNS env var, then 80.
2026-02-08 23:14:13 +01:00
29350032ba Add MIT license and fix Taskfile install path 2026-02-08 23:10:27 +01:00
0ff559c7ea Use GOBIN for one-step install in README 2026-02-08 23:08:36 +01:00
35af56ea9d Add README with installation and configuration instructions 2026-02-08 23:03:58 +01:00
ed312a3ed0 Update deps and boost test coverage from 70.8% to 90%
Update go-git v6, gopsutil v4.26.1, x/term v0.39.0, and transitive deps.
Refactor main() into testable run() function, add injectable processLister
and termWidthFunc for test isolation, and add tests covering detached HEAD,
dirty/clean worktree, empty repo, process listing errors, and terminal
width fallback.
2026-02-08 22:58:08 +01:00
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
9 changed files with 673 additions and 676 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

22
LICENSE Normal file
View File

@@ -0,0 +1,22 @@
MIT License
===========
Copyright (c) 2026 kjanat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

70
README.md Normal file
View File

@@ -0,0 +1,70 @@
# claude-statusline
A custom status line binary for
[Claude Code](https://docs.anthropic.com/en/docs/claude-code), written in Go.
Reads JSON from stdin (provided by Claude Code's status hook) and outputs a
formatted, ANSI-colored status line.
```
● Opus 4 ➜ statusline git:(master) 6k/200k
```
**What it shows:**
- Gitea service status (green/red dot)
- Model name
- Current directory
- Git branch and dirty indicator
- Context window usage
## Install
```bash
GOBIN=~/.claude go install gitea.kajkowalski.nl/kjanat/claude-statusline@latest
```
Or build from source:
```bash
git clone https://gitea.kajkowalski.nl/kjanat/claude-statusline.git
go build -C claude-statusline -ldflags="-s -w" -o ~/.claude/claude-statusline .
```
## Configure Claude Code
Add to your `~/.claude/settings.json`:
```json
{
"statusLine": {
"type": "command",
"command": "~/.claude/claude-statusline",
"padding": 0
}
}
```
## Development
Requires Go 1.25+.
```bash
# Build
task build
# Run tests
task test
# Run tests with coverage
task test:cover
# Lint
task lint
# Run with test fixture
task run
```
## License
MIT

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
@@ -115,5 +115,5 @@ tasks:
deps: [build] deps: [build]
cmds: cmds:
- mkdir -p ~/.claude - mkdir -p ~/.claude
- cp bin/{{.BINARY}} ~/.claude/{{.BINARY}} - cp bin/{{.BINARY}} ~/.claude/claude-{{.BINARY}}
- echo "Installed to ~/.claude/{{.BINARY}}" - echo "Installed to ~/.claude/claude-{{.BINARY}}"

14
go.mod
View File

@@ -3,20 +3,19 @@ module gitea.kajkowalski.nl/kjanat/claude-statusline
go 1.25.5 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-20260206150416-f623c7555599
github.com/shirou/gopsutil/v4 v4.25.11 github.com/shirou/gopsutil/v4 v4.26.1
golang.org/x/sys v0.39.0
) )
require ( require (
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudflare/circl v1.6.3 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/ebitengine/purego v0.9.1 // indirect github.com/ebitengine/purego v0.9.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg/v2 v2.0.2 // indirect github.com/go-git/gcfg/v2 v2.0.2 // indirect
github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd // indirect github.com/go-git/go-billy/v6 v6.0.0-20260207062542-7cf3dc9049c3 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect github.com/kevinburke/ssh_config v1.4.0 // indirect
@@ -28,6 +27,7 @@ require (
github.com/tklauser/go-sysconf v0.3.16 // indirect github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect github.com/tklauser/numcpus v0.11.0 // indirect
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.47.0 // indirect
golang.org/x/net v0.48.0 // indirect golang.org/x/net v0.49.0 // indirect
golang.org/x/sys v0.41.0 // indirect
) )

49
go.sum
View File

@@ -6,8 +6,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -15,29 +15,23 @@ 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/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 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 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= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= 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-20260207062542-7cf3dc9049c3 h1:SYirmki6iTQJnXONzJmHYTD0IRZIPkHSOLt7unzllQA=
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-20260207062542-7cf3dc9049c3/go.mod h1:X1oe0Z2qMsa9hkar3AAPuL9hu4Mi3ztXEjdqRhr6fcc=
github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd h1:Gd/f9cGi/3h1JOPaa6er+CkKUGyGX2DBJdFbDKVO+R0= github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20260122163445-0622d7459a67 h1:3hutPZF+/FBjR/9MdsLJ7e1mlt9pwHgwxMW7CrbmWII=
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.20260122163445-0622d7459a67/go.mod h1:xKt0pNHST9tYHvbiLxSY27CQWFwgIxBJuDrOE0JvbZw=
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251205091929-ed656e84d025 h1:24Uc4y1yxMe8V30NhshaDdCaTOw97BWVhVGH/m1+udM= github.com/go-git/go-git/v6 v6.0.0-20260206150416-f623c7555599 h1:M7Z/G+T9nx6cM1DYsPwV1IWk7zqfGFym14DwUwOB/z8=
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-20260206150416-f623c7555599/go.mod h1:EWlxLBkiFCzXNCadvt05fT9PCAE2sUedgDsvUUIo18s=
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/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 +41,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=
@@ -59,8 +51,8 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 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 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= 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.26.1 h1:TOkEyriIXk2HX9d4isZJtbjXbEjf5qyKPAzbzY0JWSo=
github.com/shirou/gopsutil/v4 v4.25.11/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU= github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.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 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
@@ -71,20 +63,19 @@ github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9R
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= 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 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
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=

69
main.go
View File

@@ -11,11 +11,8 @@ 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"
) )
const statuslineWidthOffset = 7
// TokenUsage tracks context window token consumption. // TokenUsage tracks context window token consumption.
type TokenUsage struct { type TokenUsage struct {
InputTokens int `json:"input_tokens"` InputTokens int `json:"input_tokens"`
@@ -71,57 +68,45 @@ func parseStatusInput(jsonStr string) (*StatusInput, error) {
return &data, nil return &data, nil
} }
// buildStatusLine constructs the left and right parts of the status line. // buildStatusLine constructs the formatted status line.
func buildStatusLine(data *StatusInput) (left, right string) { func buildStatusLine(data *StatusInput) string {
contextInfo := formatContextInfo(data.ContextWindow.ContextWindowSize, data.ContextWindow.CurrentUsage) contextInfo := formatContextInfo(data.ContextWindow.ContextWindowSize, data.ContextWindow.CurrentUsage)
dirName := filepath.Base(data.Workspace.CurrentDir) dirName := filepath.Base(data.Workspace.CurrentDir)
giteaStatus := getGiteaStatus() giteaStatus := getGiteaStatus()
gitInfo := getGitInfo(data.Workspace.CurrentDir) gitInfo := getGitInfo(data.Workspace.CurrentDir)
left = fmt.Sprintf("%s %s%s%s %s➜%s %s%s%s%s", left := fmt.Sprintf("%s %s%s%s %s➜%s %s%s%s%s",
giteaStatus, giteaStatus,
magenta, data.Model.DisplayName, reset, magenta, data.Model.DisplayName, reset,
boldGreen, reset, boldGreen, reset,
cyan, dirName, reset, cyan, dirName, reset,
gitInfo) gitInfo)
right = fmt.Sprintf("%s%s%s", yellow, contextInfo, reset) right := fmt.Sprintf("%s%s%s", yellow, contextInfo, reset)
return left, right
return left + " " + right
} }
// calculatePadding returns the number of spaces needed for padding. // run reads JSON from r, builds the statusline, and writes it to w.
func calculatePadding(leftVisible, rightVisible string, termWidth int) int { func run(r *bufio.Reader, w *strings.Builder) error {
return max(termWidth-len(leftVisible)-len(rightVisible), 1) jsonStr := readInputFromStdin(r)
}
// 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) data, err := parseStatusInput(jsonStr)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing JSON: %v\n", err) return fmt.Errorf("error parsing JSON: %w", err)
os.Exit(1)
} }
left, right := buildStatusLine(data) w.WriteString(buildStatusLine(data))
return nil
}
// Calculate visible lengths (strip ANSI) func main() {
leftVisible := stripANSI(left) var out strings.Builder
rightVisible := stripANSI(right) if err := run(bufio.NewReader(os.Stdin), &out); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
// Get terminal width os.Exit(1)
termWidth := getTerminalWidth() - statuslineWidthOffset }
fmt.Print(out.String())
// Calculate and apply padding
padding := calculatePadding(leftVisible, rightVisible, termWidth)
output := formatOutput(left, right, padding)
fmt.Print(output)
} }
func formatContextInfo(contextSize int, usage *TokenUsage) string { func formatContextInfo(contextSize int, usage *TokenUsage) string {
@@ -136,9 +121,13 @@ func formatContextInfo(contextSize int, usage *TokenUsage) string {
return fmt.Sprintf("%dk/%dk", currentK, totalK) return fmt.Sprintf("%dk/%dk", currentK, totalK)
} }
// processLister returns the list of running processes.
// Replaced in tests to avoid depending on real process state.
var processLister = process.Processes
func getGiteaStatus() string { func getGiteaStatus() string {
// Check if gitea process is running using gopsutil (cross-platform) // Check if gitea process is running using gopsutil (cross-platform)
procs, err := process.Processes() procs, err := processLister()
if err != nil { if err != nil {
return red + "●" + reset return red + "●" + reset
} }
@@ -196,14 +185,6 @@ func getGitInfo(cwd string) string {
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`) var ansiRegex = regexp.MustCompile(`\x1b\[[0-9;]*m`)
func stripANSI(s string) string { func stripANSI(s string) string {

File diff suppressed because it is too large Load Diff