fedora-csb-system-manager
  1package main
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6	"os"
  7	"path/filepath"
  8	"strings"
  9)
 10
 11// TodoItem represents a single TODO item from TodoWrite
 12type TodoItem struct {
 13	Content    string `json:"content"`
 14	Status     string `json:"status"`
 15	ActiveForm string `json:"activeForm"`
 16}
 17
 18// ToolResult represents the structure of tool execution results from PostToolUse hook
 19type ToolResult struct {
 20	ToolName  string          `json:"tool_name"`
 21	ToolInput json.RawMessage `json:"tool_input"`
 22}
 23
 24// TodoWriteParams represents TodoWrite tool parameters
 25type TodoWriteParams struct {
 26	Todos []TodoItem `json:"todos"`
 27}
 28
 29// SkillParams represents Skill tool parameters
 30type SkillParams struct {
 31	Skill string `json:"skill"`
 32}
 33
 34// setTerminalTitle sets the terminal tab title using ANSI escape codes
 35func setTerminalTitle(title string) {
 36	fmt.Fprintf(os.Stderr, "\x1b]0;%s\x07", title)
 37	fmt.Fprintf(os.Stderr, "\x1b]2;%s\x07", title)
 38	fmt.Fprintf(os.Stderr, "\x1b]30;%s\x07", title)
 39}
 40
 41// getProjectName extracts the project name from CLAUDE_PROJECT_DIR
 42func getProjectName() string {
 43	projectDir := os.Getenv("CLAUDE_PROJECT_DIR")
 44	if projectDir == "" {
 45		return ""
 46	}
 47	return filepath.Base(projectDir)
 48}
 49
 50// getSkillIcon returns an emoji icon for known skills
 51func getSkillIcon(skillName string) string {
 52	icons := map[string]string{
 53		"Journal":    "πŸ““",
 54		"Notes":      "πŸ“",
 55		"TODOs":      "βœ…",
 56		"Org":        "πŸ“‹",
 57		"Git":        "πŸ”§",
 58		"GitHub":     "πŸ™",
 59		"Email":      "πŸ“§",
 60		"Python":     "🐍",
 61		"golang":     "🐹",
 62		"Rust":       "πŸ¦€",
 63		"Nix":        "❄️",
 64		"Kubernetes": "☸️",
 65		"Tekton":     "πŸš€",
 66	}
 67
 68	if icon, ok := icons[skillName]; ok {
 69		return icon
 70	}
 71	return "πŸ€–"
 72}
 73
 74// getTodoProgress extracts task progress from TodoWrite parameters
 75func getTodoProgress(todos []TodoItem) (string, bool) {
 76	var inProgressTask string
 77	completedCount := 0
 78	total := len(todos)
 79
 80	if total == 0 {
 81		return "", false
 82	}
 83
 84	for _, todo := range todos {
 85		if todo.Status == "completed" {
 86			completedCount++
 87		} else if todo.Status == "in_progress" {
 88			inProgressTask = todo.ActiveForm
 89		}
 90	}
 91
 92	if inProgressTask != "" {
 93		progress := fmt.Sprintf("[%d/%d] %s", completedCount+1, total, inProgressTask)
 94		return progress, true
 95	}
 96
 97	return "", false
 98}
 99
100// buildTitle constructs the terminal title based on available context
101func buildTitle(toolResult *ToolResult) string {
102	var parts []string
103
104	// Priority 1: Check for active task from TodoWrite
105	if toolResult != nil && toolResult.ToolName == "TodoWrite" {
106		var params TodoWriteParams
107		if err := json.Unmarshal(toolResult.ToolInput, &params); err == nil {
108			if progress, ok := getTodoProgress(params.Todos); ok {
109				parts = append(parts, progress)
110			}
111		}
112	}
113
114	// Priority 2: Check for active skill
115	if toolResult != nil && toolResult.ToolName == "Skill" {
116		var params SkillParams
117		if err := json.Unmarshal(toolResult.ToolInput, &params); err == nil {
118			icon := getSkillIcon(params.Skill)
119			parts = append(parts, fmt.Sprintf("%s %s", icon, params.Skill))
120		}
121	}
122
123	// Priority 3: Add project context
124	if projectName := getProjectName(); projectName != "" {
125		parts = append(parts, projectName)
126	}
127
128	// Build final title
129	if len(parts) > 0 {
130		return fmt.Sprintf("Claude: %s", strings.Join(parts, " β€’ "))
131	}
132
133	return "Claude Ready"
134}
135
136func main() {
137	// Read tool result from stdin (if provided by hook system)
138	var toolResult ToolResult
139	decoder := json.NewDecoder(os.Stdin)
140
141	// Try to decode, but don't fail if stdin is empty
142	if err := decoder.Decode(&toolResult); err != nil {
143		// No tool result provided, just use project context
144		title := buildTitle(nil)
145		setTerminalTitle(title)
146		return
147	}
148
149	// Build and set title based on tool result
150	title := buildTitle(&toolResult)
151	setTerminalTitle(title)
152}