-
Notifications
You must be signed in to change notification settings - Fork 187
Update status tui #448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Update status tui #448
Changes from all commits
3e3a1b0
d961ec3
a9320aa
f48ef4b
7666dd9
572d386
797e36b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| package cli | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "io" | ||
| "os" | ||
| "strconv" | ||
| "strings" | ||
| "time" | ||
|
|
||
| "github.com/charmbracelet/lipgloss" | ||
| "github.com/entireio/cli/cmd/entire/cli/agent" | ||
|
|
||
| "golang.org/x/term" | ||
| ) | ||
|
|
||
| // statusStyles holds pre-built lipgloss styles and terminal metadata. | ||
| type statusStyles struct { | ||
| colorEnabled bool | ||
| width int | ||
|
|
||
| // Styles | ||
| green lipgloss.Style | ||
| red lipgloss.Style | ||
| gray lipgloss.Style | ||
| bold lipgloss.Style | ||
| dim lipgloss.Style | ||
| agent lipgloss.Style // amber/orange for agent names | ||
| cyan lipgloss.Style | ||
| } | ||
|
|
||
| // newStatusStyles creates styles appropriate for the output writer. | ||
| func newStatusStyles(w io.Writer) statusStyles { | ||
| useColor := shouldUseColor(w) | ||
| width := getTerminalWidth(w) | ||
|
|
||
| s := statusStyles{ | ||
| colorEnabled: useColor, | ||
| width: width, | ||
| } | ||
|
|
||
| if useColor { | ||
| s.green = lipgloss.NewStyle().Foreground(lipgloss.Color("2")) | ||
| s.red = lipgloss.NewStyle().Foreground(lipgloss.Color("1")) | ||
| s.gray = lipgloss.NewStyle().Foreground(lipgloss.Color("8")) | ||
| s.bold = lipgloss.NewStyle().Bold(true) | ||
| s.dim = lipgloss.NewStyle().Faint(true) | ||
| s.agent = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("214")) | ||
| s.cyan = lipgloss.NewStyle().Foreground(lipgloss.Color("6")) | ||
| } | ||
|
|
||
| return s | ||
| } | ||
|
|
||
| // render applies a style to text only when color is enabled. | ||
| func (s statusStyles) render(style lipgloss.Style, text string) string { | ||
| if !s.colorEnabled { | ||
| return text | ||
| } | ||
| return style.Render(text) | ||
| } | ||
|
|
||
| // shouldUseColor returns true if the writer supports color output. | ||
| func shouldUseColor(w io.Writer) bool { | ||
| if os.Getenv("NO_COLOR") != "" { | ||
| return false | ||
| } | ||
| if f, ok := w.(*os.File); ok { | ||
| return term.IsTerminal(int(f.Fd())) | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| // getTerminalWidth returns the terminal width, capped at 80 with a fallback of 60. | ||
| // It first checks the writer itself, then falls back to Stdout/Stderr. | ||
| func getTerminalWidth(w io.Writer) int { | ||
| // Try the output writer first | ||
| if f, ok := w.(*os.File); ok { | ||
| if width, _, err := term.GetSize(int(f.Fd())); err == nil && width > 0 { | ||
| return min(width, 80) | ||
| } | ||
| } | ||
|
|
||
| // Fall back to Stdout, then Stderr | ||
| for _, f := range []*os.File{os.Stdout, os.Stderr} { | ||
| if f == nil { | ||
| continue | ||
| } | ||
| if width, _, err := term.GetSize(int(f.Fd())); err == nil && width > 0 { | ||
| return min(width, 80) | ||
| } | ||
| } | ||
|
|
||
| return 60 | ||
| } | ||
|
|
||
| // formatTokenCount formats a token count for display. | ||
| // 0 → "0", 500 → "500", 1200 → "1.2k", 14300 → "14.3k" | ||
| func formatTokenCount(n int) string { | ||
| if n < 1000 { | ||
| return strconv.Itoa(n) | ||
| } | ||
| f := float64(n) / 1000.0 | ||
| s := fmt.Sprintf("%.1f", f) | ||
| // Remove trailing ".0" for clean display (e.g., 1000 → "1k" not "1.0k") | ||
| s = strings.TrimSuffix(s, ".0") | ||
| return s + "k" | ||
| } | ||
|
|
||
| // totalTokens recursively sums all token fields including subagent tokens. | ||
| func totalTokens(tu *agent.TokenUsage) int { | ||
| if tu == nil { | ||
| return 0 | ||
| } | ||
| total := tu.InputTokens + tu.CacheCreationTokens + tu.CacheReadTokens + tu.OutputTokens | ||
| total += totalTokens(tu.SubagentTokens) | ||
| return total | ||
| } | ||
|
|
||
| // horizontalRule renders a dimmed horizontal rule of the given width. | ||
| func (s statusStyles) horizontalRule(width int) string { | ||
| rule := strings.Repeat("─", width) | ||
| return s.render(s.dim, rule) | ||
| } | ||
|
|
||
| // sectionRule renders a section header like: ── Active Sessions ──────────── | ||
| func (s statusStyles) sectionRule(label string, width int) string { | ||
| prefix := "── " | ||
| content := label + " " | ||
| usedWidth := len([]rune(prefix)) + len([]rune(content)) | ||
| trailing := width - usedWidth | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Byte length used instead of display width for UnicodeLow Severity
|
||
| if trailing < 1 { | ||
| trailing = 1 | ||
| } | ||
|
|
||
| var b strings.Builder | ||
| b.WriteString(s.render(s.dim, "── ")) | ||
| b.WriteString(s.render(s.dim, label)) | ||
| b.WriteString(" ") | ||
| b.WriteString(s.render(s.dim, strings.Repeat("─", trailing))) | ||
| return b.String() | ||
| } | ||
|
|
||
| // activeTimeDisplay formats a last interaction time for display. | ||
| // Returns "active now" for recent activity (<1min), otherwise "active Xm ago". | ||
| func activeTimeDisplay(lastInteraction *time.Time) string { | ||
| if lastInteraction == nil { | ||
| return "" | ||
| } | ||
| d := time.Since(*lastInteraction) | ||
| if d < time.Minute { | ||
| return "active now" | ||
| } | ||
| return "active " + timeAgo(*lastInteraction) | ||
| } | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused
grayfield defined but never referencedLow Severity
The
grayfield instatusStylesis defined and initialized but never read anywhere. SincestatusStylesis a new unexported type introduced in this PR, all usage is visible in the diff —sty.green,sty.red,sty.bold,sty.dim,sty.agent, andsty.cyanare all referenced, butsty.grayis not. This is dead code.Additional Locations (1)
cmd/entire/cli/status_style.go#L44-L45