feat: auto-detect non-TTY and adjust output (bd-xrwy)

Add TTY detection to automatically disable ANSI colors when stdout is
piped or redirected. Respects standard conventions:
- NO_COLOR environment variable (no-color.org)
- CLICOLOR=0 disables color
- CLICOLOR_FORCE enables color even in non-TTY

Also adds ShouldUseEmoji() helper controlled by BD_NO_EMOJI.
Pager already disabled non-TTY in previous work (bd-jdz3).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-30 06:58:50 -08:00
parent 06c8855873
commit f6f9ef260d
3 changed files with 199 additions and 0 deletions

52
internal/ui/terminal.go Normal file
View File

@@ -0,0 +1,52 @@
// Package ui provides terminal styling and output helpers for beads CLI.
package ui
import (
"os"
"golang.org/x/term"
)
// IsTerminal returns true if stdout is connected to a terminal (TTY).
func IsTerminal() bool {
return term.IsTerminal(int(os.Stdout.Fd()))
}
// ShouldUseColor determines if ANSI color codes should be used.
// Respects standard conventions:
// - NO_COLOR: https://no-color.org/ - disables color if set
// - CLICOLOR=0: disables color
// - CLICOLOR_FORCE: forces color even in non-TTY
// - Falls back to TTY detection
func ShouldUseColor() bool {
// NO_COLOR standard - any value disables color
if os.Getenv("NO_COLOR") != "" {
return false
}
// CLICOLOR=0 disables color
if os.Getenv("CLICOLOR") == "0" {
return false
}
// CLICOLOR_FORCE forces color even in non-TTY
if os.Getenv("CLICOLOR_FORCE") != "" {
return true
}
// Default: use color only if stdout is a TTY
return IsTerminal()
}
// ShouldUseEmoji determines if emoji decorations should be used.
// Disabled in non-TTY mode to keep output machine-readable.
// Can be controlled with BD_NO_EMOJI environment variable.
func ShouldUseEmoji() bool {
// Explicit disable
if os.Getenv("BD_NO_EMOJI") != "" {
return false
}
// Default: use emoji only if stdout is a TTY
return IsTerminal()
}