theniceboy/agent-tracker/cmd/agent/tmux_status.go
2026-07-07 15:55:11 +08:00

862 lines
23 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"bufio"
"encoding/json"
"flag"
"fmt"
"math"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
const (
statusRightModuleCPU = "cpu"
statusRightModuleNetwork = "network"
statusRightModuleMemory = "memory"
statusRightModuleMemoryTotals = "memory_totals"
statusRightModuleAgent = "agent"
statusRightModuleTodoPreview = "todo_preview"
statusRightModuleTodos = "todos"
statusRightModuleFlashMoe = "flash_moe"
statusRightModuleHost = "host"
statusRightModuleGoal = "goal"
)
const (
statusIconCPU = ""
statusIconNetwork = "󰖩"
statusIconMemory = ""
statusIconWindow = "󰖲"
statusIconSession = ""
statusIconTotal = "󰍛"
statusIconAgent = "󰚩"
statusIconTodos = "󰎚"
statusIconFlashMoe = "󱙺"
statusIconGoal = "⌖"
)
func statusRightModules() []string {
return []string{
statusRightModuleCPU,
statusRightModuleNetwork,
statusRightModuleMemory,
statusRightModuleMemoryTotals,
statusRightModuleAgent,
statusRightModuleTodos,
statusRightModuleTodoPreview,
statusRightModuleFlashMoe,
statusRightModuleHost,
statusRightModuleGoal,
}
}
var cpuUsagePattern = regexp.MustCompile(`CPU usage:\s*([0-9.]+)% user,\s*([0-9.]+)% sys,`)
var statusCommandOutput = func(name string, args ...string) ([]byte, error) {
cmd := exec.Command(name, args...)
return cmd.Output()
}
var statusCommandStart = func(name string, args ...string) error {
cmd := exec.Command(name, args...)
if err := cmd.Start(); err != nil {
return err
}
if cmd.Process != nil {
_ = cmd.Process.Release()
}
return nil
}
var statusNow = time.Now
var statusHostname = os.Hostname
var statusDetectCurrentAgentFromTmux = detectCurrentAgentFromTmux
var statusLoadRegistry = loadRegistry
var statusMemoryCachePath = func() string { return "/tmp/tmux-mem-usage.json" }
var statusMemoryCacheRefreshScript = func() string {
return filepath.Join(os.Getenv("HOME"), ".config", "tmux", "tmux-status", "mem_usage_cache.py")
}
var statusTodoFilePath = func() string {
return filepath.Join(os.Getenv("HOME"), ".cache", "agent", "todos.json")
}
var statusFlashMoeMetricsPath = func() string {
return filepath.Join(os.Getenv("HOME"), ".flash-moe", "tmux_metrics")
}
var statusNetworkRateCachePath = func() string {
return "/tmp/agent-tmux-network-rate.json"
}
type tmuxRightStatusArgs struct {
Width int
StatusBG string
SessionName string
WindowIndex string
PaneID string
WindowID string
}
type statusSegment struct {
FG string
BG string
Text string
Bold bool
}
type statusMemoryCache struct {
Pane map[string]string `json:"pane"`
Window map[string]string `json:"window"`
Session map[string]string `json:"session"`
Total string `json:"total"`
}
type statusTodoCache struct {
Global []statusTodoItem `json:"global"`
Sessions map[string][]statusTodoItem `json:"sessions"`
Windows map[string][]statusTodoItem `json:"windows"`
}
type statusTodoItem struct {
Title string `json:"title"`
Done bool `json:"done"`
}
type statusNetworkCounter struct {
InBytes uint64
OutBytes uint64
}
type statusNetworkRateCache struct {
Interface string `json:"interface"`
InBytes uint64 `json:"in_bytes"`
OutBytes uint64 `json:"out_bytes"`
SampledAt int64 `json:"sampled_at_unix_ms"`
}
func runTmuxRightStatus(args []string) error {
fs := flag.NewFlagSet("agent tmux right-status", flag.ContinueOnError)
fs.SetOutput(os.Stderr)
if err := fs.Parse(args); err != nil {
return err
}
values := fs.Args()
parsed := tmuxRightStatusArgs{}
if len(values) > 0 {
parsed.Width, _ = strconv.Atoi(strings.TrimSpace(values[0]))
}
if len(values) > 1 {
parsed.StatusBG = strings.TrimSpace(values[1])
}
if len(values) > 2 {
parsed.SessionName = strings.TrimSpace(values[2])
}
if len(values) > 3 {
parsed.WindowIndex = strings.TrimSpace(values[3])
}
if len(values) > 4 {
parsed.PaneID = strings.TrimSpace(values[4])
}
if len(values) > 5 {
parsed.WindowID = strings.TrimSpace(values[5])
}
if parsed.StatusBG == "" || parsed.StatusBG == "default" {
parsed.StatusBG = "black"
}
if parsed.Width > 0 && parsed.Width < statusRightMinimumWidth() {
return nil
}
fmt.Print(renderTmuxRightStatus(parsed))
return nil
}
func renderTmuxRightStatus(args tmuxRightStatusArgs) string {
segments := make([]statusSegment, 0, 6)
if statusRightModuleEnabled(statusRightModuleCPU) {
if label := loadCPUStatusLabel(); label != "" {
segments = append(segments, statusSegment{FG: "#1d1f21", BG: "#d08770", Text: label, Bold: true})
}
}
if statusRightModuleEnabled(statusRightModuleNetwork) {
if label := loadNetworkStatusLabel(); label != "" {
segments = append(segments, statusSegment{FG: "#1d1f21", BG: "#8fbcbb", Text: label, Bold: true})
}
}
if statusRightModuleEnabled(statusRightModuleMemory) {
if label := loadMemoryStatusLabel(args.PaneID); label != "" {
segments = append(segments, statusSegment{FG: "#eceff4", BG: "#5e81ac", Text: label})
}
}
if statusRightModuleEnabled(statusRightModuleMemoryTotals) {
segments = append(segments, loadMemoryTotalsStatusSegments(args)...)
}
if statusRightModuleEnabled(statusRightModuleAgent) {
if label := loadAgentStatusLabel(args.WindowID); label != "" {
segments = append(segments, statusSegment{FG: "#1d1f21", BG: "#81a1c1", Text: label, Bold: true})
}
}
if statusRightModuleEnabled(statusRightModuleTodos) {
if label := loadTodosStatusLabel(args.WindowID); label != "" {
segments = append(segments, statusSegment{FG: "#1d1f21", BG: "#cc6666", Text: label, Bold: true})
}
}
if statusRightModuleEnabled(statusRightModuleGoal) {
if label := loadGoalStatusLabel(args.WindowID); label != "" {
segments = append(segments, statusSegment{FG: "#1d1f21", BG: "#a3be8c", Text: label, Bold: true})
}
}
if statusRightModuleEnabled(statusRightModuleFlashMoe) {
if segment, ok := loadFlashMoeStatusSegment(); ok {
segments = append(segments, segment)
}
}
if statusRightModuleEnabled(statusRightModuleHost) {
if label := loadHostStatusLabel(); label != "" {
segments = append(segments, statusSegment{FG: "#1d1f21", BG: statusThemeColor(), Text: label})
}
}
return formatRightStatusSegments(args.StatusBG, segments)
}
func formatRightStatusSegments(statusBG string, segments []statusSegment) string {
if len(segments) == 0 {
return ""
}
separator := ""
rightCap := "█"
prevBG := statusBG
var builder strings.Builder
for _, segment := range segments {
builder.WriteString(fmt.Sprintf("#[fg=%s,bg=%s]%s#[fg=%s,bg=%s", segment.BG, prevBG, separator, segment.FG, segment.BG))
if segment.Bold {
builder.WriteString(",bold")
}
builder.WriteString("]")
builder.WriteString(segment.Text)
prevBG = segment.BG
}
builder.WriteString(fmt.Sprintf(" #[fg=%s,bg=%s]%s", prevBG, statusBG, rightCap))
return builder.String()
}
func statusRightMinimumWidth() int {
value := strings.TrimSpace(os.Getenv("TMUX_RIGHT_MIN_WIDTH"))
if value == "" {
return 90
}
parsed, err := strconv.Atoi(value)
if err != nil || parsed < 1 {
return 90
}
return parsed
}
func statusThemeColor() string {
value := strings.TrimSpace(os.Getenv("TMUX_THEME_COLOR"))
if value == "" {
return "#b294bb"
}
return value
}
func loadCPUStatusLabel() string {
output, err := statusCommandOutput("top", "-l", "1", "-n", "0")
if err != nil {
return ""
}
total, ok := parseCPUUsageTotal(string(output))
if !ok {
return ""
}
return fmt.Sprintf(" %s %s ", statusIconCPU, formatUsagePercent(total))
}
func parseCPUUsageTotal(output string) (float64, bool) {
matches := cpuUsagePattern.FindStringSubmatch(output)
if len(matches) != 3 {
return 0, false
}
user, err := strconv.ParseFloat(matches[1], 64)
if err != nil {
return 0, false
}
system, err := strconv.ParseFloat(matches[2], 64)
if err != nil {
return 0, false
}
total := math.Max(0, user+system)
if total > 100 {
total = 100
}
return total, true
}
func formatUsagePercent(value float64) string {
if value < 10 && math.Abs(value-math.Round(value)) > 0.05 {
return fmt.Sprintf("%.1f%%", value)
}
return fmt.Sprintf("%.0f%%", value)
}
func loadNetworkStatusLabel() string {
preferred := loadPrimaryNetworkInterface()
output, err := statusCommandOutput("netstat", "-ibn")
if err != nil {
return ""
}
counters := parseNetworkCounters(string(output))
iface, current, ok := pickNetworkCounter(counters, preferred)
if !ok {
return ""
}
now := statusNow().UnixMilli()
previous, _ := loadNetworkRateCache()
rate := " ↓-- ↑-- "
if previous.Interface == iface && previous.SampledAt > 0 && now > previous.SampledAt && current.InBytes >= previous.InBytes && current.OutBytes >= previous.OutBytes {
seconds := float64(now-previous.SampledAt) / 1000
if seconds >= 0.25 {
down := float64(current.InBytes-previous.InBytes) / seconds
up := float64(current.OutBytes-previous.OutBytes) / seconds
rate = fmt.Sprintf(" %s ↓%s ↑%s ", statusIconNetwork, formatByteRate(down), formatByteRate(up))
}
}
_ = saveNetworkRateCache(statusNetworkRateCache{
Interface: iface,
InBytes: current.InBytes,
OutBytes: current.OutBytes,
SampledAt: now,
})
if rate == " ↓-- ↑-- " {
return fmt.Sprintf(" %s ↓-- ↑-- ", statusIconNetwork)
}
return rate
}
func loadPrimaryNetworkInterface() string {
output, err := statusCommandOutput("route", "-n", "get", "default")
if err != nil {
return ""
}
scanner := bufio.NewScanner(strings.NewReader(string(output)))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if !strings.HasPrefix(line, "interface:") {
continue
}
return strings.TrimSpace(strings.TrimPrefix(line, "interface:"))
}
return ""
}
func parseNetworkCounters(output string) map[string]statusNetworkCounter {
counters := make(map[string]statusNetworkCounter)
scanner := bufio.NewScanner(strings.NewReader(output))
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) < 10 {
continue
}
name := strings.TrimSuffix(strings.TrimSpace(fields[0]), "*")
if name == "" || strings.EqualFold(name, "Name") {
continue
}
if _, exists := counters[name]; exists {
continue
}
inBytes, err := strconv.ParseUint(fields[6], 10, 64)
if err != nil {
continue
}
outBytes, err := strconv.ParseUint(fields[9], 10, 64)
if err != nil {
continue
}
counters[name] = statusNetworkCounter{InBytes: inBytes, OutBytes: outBytes}
}
return counters
}
func pickNetworkCounter(counters map[string]statusNetworkCounter, preferred string) (string, statusNetworkCounter, bool) {
preferred = strings.TrimSpace(preferred)
if preferred != "" {
if counter, ok := counters[preferred]; ok {
return preferred, counter, true
}
}
var bestName string
var bestCounter statusNetworkCounter
var bestTotal uint64
for name, counter := range counters {
if ignoreNetworkInterface(name) {
continue
}
total := counter.InBytes + counter.OutBytes
if total <= bestTotal {
continue
}
bestName = name
bestCounter = counter
bestTotal = total
}
if bestName == "" {
return "", statusNetworkCounter{}, false
}
return bestName, bestCounter, true
}
func ignoreNetworkInterface(name string) bool {
for _, prefix := range []string{"lo", "awdl", "llw", "gif", "stf", "anpi", "ap", "bridge", "pktap"} {
if strings.HasPrefix(name, prefix) {
return true
}
}
return false
}
func formatByteRate(bytesPerSecond float64) string {
if bytesPerSecond < 0 {
bytesPerSecond = 0
}
units := []string{"B", "K", "M", "G"}
value := bytesPerSecond
unit := units[0]
for _, candidate := range units[1:] {
if value < 1024 {
break
}
value /= 1024
unit = candidate
}
if value < 10 && unit != "B" {
return fmt.Sprintf("%.1f%s", value, unit)
}
return fmt.Sprintf("%.0f%s", value, unit)
}
func loadNetworkRateCache() (statusNetworkRateCache, error) {
data, err := os.ReadFile(statusNetworkRateCachePath())
if err != nil {
return statusNetworkRateCache{}, err
}
var cache statusNetworkRateCache
if err := json.Unmarshal(data, &cache); err != nil {
return statusNetworkRateCache{}, err
}
return cache, nil
}
func saveNetworkRateCache(cache statusNetworkRateCache) error {
data, err := json.Marshal(cache)
if err != nil {
return err
}
path := statusNetworkRateCachePath()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
tmpPath := path + ".tmp"
if err := os.WriteFile(tmpPath, data, 0o644); err != nil {
return err
}
return os.Rename(tmpPath, path)
}
func loadMemoryStatusLabel(paneID string) string {
paneID = strings.TrimSpace(paneID)
if paneID == "" {
return ""
}
cache, ok := loadMemoryStatusCache()
if !ok {
return ""
}
value := strings.TrimSpace(cache.Pane[paneID])
if value == "" {
return ""
}
return fmt.Sprintf(" %s %s ", statusIconMemory, value)
}
func loadMemoryTotalsStatusSegments(args tmuxRightStatusArgs) []statusSegment {
cache, ok := loadMemoryStatusCache()
if !ok {
return nil
}
segments := make([]statusSegment, 0, 3)
windowKey := strings.TrimSpace(args.SessionName)
if windowKey != "" && strings.TrimSpace(args.WindowIndex) != "" {
windowKey = windowKey + ":" + strings.TrimSpace(args.WindowIndex)
}
if value := strings.TrimSpace(cache.Window[windowKey]); value != "" {
segments = append(segments, statusSegment{FG: "#eceff4", BG: "#4c566a", Text: fmt.Sprintf(" %s %s ", statusIconWindow, value)})
}
if value := strings.TrimSpace(cache.Session[strings.TrimSpace(args.SessionName)]); value != "" {
segments = append(segments, statusSegment{FG: "#eceff4", BG: "#434c5e", Text: fmt.Sprintf(" %s %s ", statusIconSession, value)})
}
if value := strings.TrimSpace(cache.Total); value != "" {
segments = append(segments, statusSegment{FG: "#eceff4", BG: "#3b4252", Text: fmt.Sprintf(" %s %s ", statusIconTotal, value)})
}
return segments
}
func loadMemoryStatusCache() (statusMemoryCache, bool) {
refreshMemoryUsageCache()
data, err := os.ReadFile(statusMemoryCachePath())
if err != nil {
return statusMemoryCache{}, false
}
var cache statusMemoryCache
if err := json.Unmarshal(data, &cache); err != nil {
return statusMemoryCache{}, false
}
return cache, true
}
func loadAgentStatusLabel(windowID string) string {
windowID = strings.TrimSpace(windowID)
if windowID == "" {
return ""
}
ref, err := statusDetectCurrentAgentFromTmux(windowID)
if err != nil || strings.TrimSpace(ref.ID) == "" {
return ""
}
reg, err := statusLoadRegistry()
if err != nil || reg == nil {
return ""
}
record := reg.Agents[strings.TrimSpace(ref.ID)]
if record == nil {
return ""
}
device := strings.TrimSpace(record.Device)
if device == "" {
device = "no device"
}
return fmt.Sprintf(" %s %s ", statusIconAgent, device)
}
// loadGoalStatusLabel renders the focused window's goal path + thread name.
func loadGoalStatusLabel(windowID string) string {
windowID = strings.TrimSpace(windowID)
if windowID == "" {
return ""
}
store, err := loadGoalStore()
if err != nil || store == nil {
return ""
}
thread := findThreadByWindow(store, windowID)
if thread == nil {
return ""
}
path := goalPathTitles(store, thread.GoalID)
if len(path) == 0 {
return ""
}
label := strings.Join(path, " ")
return fmt.Sprintf(" %s %s ", statusIconGoal, truncate(label, 40))
}
// goalPathTitles returns ancestor goal titles from root to the given goal.
func goalPathTitles(store *GoalStore, goalID string) []string {
goalID = strings.TrimSpace(goalID)
if goalID == "" || store == nil {
return nil
}
var titles []string
seen := map[string]bool{}
for goalID != "" && !seen[goalID] {
g, ok := store.Goals[goalID]
if !ok {
break
}
seen[goalID] = true
titles = append([]string{strings.TrimSpace(g.Title)}, titles...)
goalID = strings.TrimSpace(g.ParentID)
}
return titles
}
func refreshMemoryUsageCache() {
script := statusMemoryCacheRefreshScript()
if strings.TrimSpace(script) == "" || !fileExists(script) {
return
}
_ = statusCommandStart("python3", script)
}
func loadTodosStatusLabel(windowID string) string {
items, ok := statusTodoItemsForWindow(windowID)
if !ok {
return ""
}
count := 0
for _, item := range items {
if !item.Done {
count++
}
}
if count == 0 {
return ""
}
if !statusRightModuleEnabled(statusRightModuleTodoPreview) {
return fmt.Sprintf(" %s %d ", statusIconTodos, count)
}
title := firstOpenStatusTodoTitle(items)
if title != "" {
return fmt.Sprintf(" %s %d %s ", statusIconTodos, count, truncate(title, statusTodoMaxChars()))
}
return fmt.Sprintf(" %s %d ", statusIconTodos, count)
}
func loadStatusTodoCache() (statusTodoCache, bool) {
data, err := os.ReadFile(statusTodoFilePath())
if err != nil {
return statusTodoCache{}, false
}
var cache statusTodoCache
if err := json.Unmarshal(data, &cache); err != nil {
return statusTodoCache{}, false
}
if cache.Global == nil {
cache.Global = []statusTodoItem{}
}
if cache.Sessions == nil {
cache.Sessions = map[string][]statusTodoItem{}
}
if cache.Windows == nil {
cache.Windows = map[string][]statusTodoItem{}
}
return cache, true
}
func statusTodoItemsForWindow(windowID string) ([]statusTodoItem, bool) {
windowID = strings.TrimSpace(windowID)
if windowID == "" {
return nil, false
}
cache, ok := loadStatusTodoCache()
if !ok {
return nil, false
}
return cache.Windows[windowID], true
}
func firstOpenStatusTodoTitle(items []statusTodoItem) string {
for _, item := range items {
if item.Done {
continue
}
title := strings.Join(strings.Fields(strings.TrimSpace(item.Title)), " ")
if title != "" {
return title
}
}
return ""
}
func statusTodoMaxChars() int {
value := strings.TrimSpace(os.Getenv("TMUX_TODO_MAX_CHARS"))
if value == "" {
return 32
}
parsed, err := strconv.Atoi(value)
if err != nil || parsed < 8 {
return 32
}
return parsed
}
func loadFlashMoeStatusSegment() (statusSegment, bool) {
metricsPath := statusFlashMoeMetricsPath()
if strings.TrimSpace(metricsPath) == "" {
return statusSegment{}, false
}
data, err := os.ReadFile(metricsPath)
if err != nil {
return statusSegment{}, false
}
values := map[string]string{}
scanner := bufio.NewScanner(strings.NewReader(string(data)))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
key, value, ok := strings.Cut(line, "=")
if !ok {
continue
}
values[strings.TrimSpace(key)] = strings.TrimSpace(value)
}
phase := values["phase"]
if updated := strings.TrimSpace(values["updated_ms"]); updated != "" {
if updatedMS, err := strconv.ParseInt(updated, 10, 64); err == nil {
ageMS := statusNow().UnixMilli() - updatedMS
if ageMS > 10000 && (phase == "gen" || phase == "prefill") {
phase = "idle"
}
}
}
switch phase {
case "prefill":
promptTokens := strings.TrimSpace(values["prompt_tokens"])
label := fmt.Sprintf(" %s prefill ", statusIconFlashMoe)
if promptTokens != "" && promptTokens != "0" {
label = fmt.Sprintf(" %s prefill:%s ", statusIconFlashMoe, promptTokens)
}
return statusSegment{FG: "#1d1f21", BG: "#ebcb8b", Text: label, Bold: true}, true
case "gen":
tokS := strings.TrimSpace(values["tok_s"])
label := fmt.Sprintf(" %s gen ", statusIconFlashMoe)
if tokS != "" && tokS != "0.00" {
label = fmt.Sprintf(" %s %s tok/s ", statusIconFlashMoe, tokS)
}
return statusSegment{FG: "#1d1f21", BG: "#a3be8c", Text: label, Bold: true}, true
default:
return statusSegment{}, false
}
}
func loadHostStatusLabel() string {
host, err := statusHostname()
if err != nil {
return ""
}
host = strings.TrimSpace(host)
if host == "" {
return ""
}
if short, _, ok := strings.Cut(host, "."); ok {
host = short
}
return fmt.Sprintf(" %s", host)
}
func defaultStatusRightModuleEnabled(module string) bool {
switch module {
case statusRightModuleCPU, statusRightModuleNetwork, statusRightModuleMemory, statusRightModuleAgent, statusRightModuleTodoPreview, statusRightModuleTodos, statusRightModuleFlashMoe, statusRightModuleHost, statusRightModuleGoal:
return true
case statusRightModuleMemoryTotals:
return true
default:
return false
}
}
func isValidStatusRightModule(module string) bool {
switch module {
case statusRightModuleCPU, statusRightModuleNetwork, statusRightModuleMemory, statusRightModuleMemoryTotals, statusRightModuleAgent, statusRightModuleTodoPreview, statusRightModuleTodos, statusRightModuleFlashMoe, statusRightModuleHost, statusRightModuleGoal:
return true
default:
return false
}
}
func statusRightModuleEnabled(module string) bool {
if !isValidStatusRightModule(module) {
return false
}
cfg := loadAppConfig()
if cfg.StatusRight == nil {
return defaultStatusRightModuleEnabled(module)
}
return cfg.StatusRight.moduleEnabled(module)
}
func (cfg statusRightConfig) moduleEnabled(module string) bool {
switch module {
case statusRightModuleCPU:
return derefBool(cfg.CPU, defaultStatusRightModuleEnabled(module))
case statusRightModuleNetwork:
return derefBool(cfg.Network, defaultStatusRightModuleEnabled(module))
case statusRightModuleMemory:
return derefBool(cfg.Memory, defaultStatusRightModuleEnabled(module))
case statusRightModuleMemoryTotals:
return derefBool(cfg.MemoryTotals, defaultStatusRightModuleEnabled(module))
case statusRightModuleAgent:
return derefBool(cfg.Agent, defaultStatusRightModuleEnabled(module))
case statusRightModuleTodoPreview:
return derefBool(cfg.TodoPreview, defaultStatusRightModuleEnabled(module))
case statusRightModuleTodos:
return derefBool(cfg.Todos, defaultStatusRightModuleEnabled(module))
case statusRightModuleFlashMoe:
return derefBool(cfg.FlashMoe, defaultStatusRightModuleEnabled(module))
case statusRightModuleHost:
return derefBool(cfg.Host, defaultStatusRightModuleEnabled(module))
case statusRightModuleGoal:
return derefBool(cfg.Goal, defaultStatusRightModuleEnabled(module))
default:
return false
}
}
func toggleStatusRightModule(module string) error {
if !isValidStatusRightModule(module) {
return fmt.Errorf("unknown status-right module: %s", module)
}
enabled := !statusRightModuleEnabled(module)
return updateAppConfig(func(cfg *appConfig) {
if cfg.StatusRight == nil {
cfg.StatusRight = &statusRightConfig{}
}
cfg.StatusRight.setModuleEnabled(module, enabled)
if cfg.StatusRight.isDefault() {
cfg.StatusRight = nil
}
})
}
func (cfg *statusRightConfig) setModuleEnabled(module string, enabled bool) {
value := boolPtr(enabled)
if enabled == defaultStatusRightModuleEnabled(module) {
value = nil
}
switch module {
case statusRightModuleCPU:
cfg.CPU = value
case statusRightModuleNetwork:
cfg.Network = value
case statusRightModuleMemory:
cfg.Memory = value
case statusRightModuleMemoryTotals:
cfg.MemoryTotals = value
case statusRightModuleAgent:
cfg.Agent = value
case statusRightModuleTodoPreview:
cfg.TodoPreview = value
case statusRightModuleTodos:
cfg.Todos = value
case statusRightModuleFlashMoe:
cfg.FlashMoe = value
case statusRightModuleHost:
cfg.Host = value
case statusRightModuleGoal:
cfg.Goal = value
}
}
func (cfg *statusRightConfig) isDefault() bool {
if cfg == nil {
return true
}
return cfg.CPU == nil && cfg.Network == nil && cfg.Memory == nil && cfg.MemoryTotals == nil && cfg.Agent == nil && cfg.TodoPreview == nil && cfg.Todos == nil && cfg.FlashMoe == nil && cfg.Host == nil && cfg.Goal == nil
}
func derefBool(value *bool, fallback bool) bool {
if value == nil {
return fallback
}
return *value
}
func boolPtr(value bool) *bool {
ptr := new(bool)
*ptr = value
return ptr
}