auto-update-daily-20260202
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, ¶ms); 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, ¶ms); 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}