flake-update-20260201
1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "io"
7 "os"
8 "path/filepath"
9 "time"
10
11 "github.com/vdemeester/home/tools/claude-hooks/internal/paths"
12)
13
14// ToolUseData represents the input from PostToolUse hook
15type ToolUseData struct {
16 ToolName string `json:"tool_name"`
17 ToolInput map[string]interface{} `json:"tool_input"`
18 ToolResponse map[string]interface{} `json:"tool_response"`
19 ConversationID string `json:"conversation_id"`
20 Timestamp string `json:"timestamp"`
21}
22
23// CaptureEntry represents a log entry in JSONL format
24type CaptureEntry struct {
25 Timestamp string `json:"timestamp"`
26 Tool string `json:"tool"`
27 Input map[string]interface{} `json:"input"`
28 Output map[string]interface{} `json:"output"`
29 Session string `json:"session"`
30}
31
32// List of tools to capture
33var interestingTools = map[string]bool{
34 "Bash": true,
35 "Edit": true,
36 "Write": true,
37 "Read": true,
38 "Task": true,
39 "NotebookEdit": true,
40 "Skill": true,
41 "SlashCommand": true,
42}
43
44func main() {
45 // Read input from stdin
46 input, err := io.ReadAll(os.Stdin)
47 if err != nil {
48 fmt.Fprintf(os.Stderr, "[capture-tool-output] Error reading stdin: %v\n", err)
49 os.Exit(0) // Silent failure - don't disrupt workflow
50 }
51
52 if len(input) == 0 {
53 os.Exit(0)
54 }
55
56 var data ToolUseData
57 if err := json.Unmarshal(input, &data); err != nil {
58 fmt.Fprintf(os.Stderr, "[capture-tool-output] Error parsing JSON: %v\n", err)
59 os.Exit(0) // Silent failure
60 }
61
62 // Only capture interesting tools
63 if !interestingTools[data.ToolName] {
64 os.Exit(0)
65 }
66
67 // Get today's date for organization
68 now := time.Now()
69 today := now.Format("2006-01-02")
70 yearMonth := now.Format("2006-01")
71
72 // Ensure capture directory exists
73 dateDir := filepath.Join(paths.HistoryDir(), "tool-outputs", yearMonth)
74 if err := os.MkdirAll(dateDir, 0755); err != nil {
75 fmt.Fprintf(os.Stderr, "[capture-tool-output] Error creating directory: %v\n", err)
76 os.Exit(0) // Silent failure
77 }
78
79 // Format output as JSONL
80 captureFile := filepath.Join(dateDir, fmt.Sprintf("%s_tool-outputs.jsonl", today))
81
82 timestamp := data.Timestamp
83 if timestamp == "" {
84 timestamp = now.Format(time.RFC3339)
85 }
86
87 entry := CaptureEntry{
88 Timestamp: timestamp,
89 Tool: data.ToolName,
90 Input: data.ToolInput,
91 Output: data.ToolResponse,
92 Session: data.ConversationID,
93 }
94
95 jsonData, err := json.Marshal(entry)
96 if err != nil {
97 fmt.Fprintf(os.Stderr, "[capture-tool-output] Error marshaling JSON: %v\n", err)
98 os.Exit(0) // Silent failure
99 }
100
101 // Append to daily log
102 f, err := os.OpenFile(captureFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
103 if err != nil {
104 fmt.Fprintf(os.Stderr, "[capture-tool-output] Error opening file: %v\n", err)
105 os.Exit(0) // Silent failure
106 }
107 defer f.Close()
108
109 if _, err := f.WriteString(string(jsonData) + "\n"); err != nil {
110 fmt.Fprintf(os.Stderr, "[capture-tool-output] Error writing to file: %v\n", err)
111 os.Exit(0) // Silent failure
112 }
113
114 // Desktop notifications disabled - they were too noisy
115 // notifyMsg := fmt.Sprintf("Tool executed: %s", data.ToolName)
116 // cmd := exec.Command("notify-send", "-u", "low", "Claude Hook", notifyMsg)
117 // if err := cmd.Run(); err != nil {
118 // // Silent failure - don't break workflow
119 // fmt.Fprintf(os.Stderr, "[capture-tool-output] Warning: Could not send notification: %v\n", err)
120 // }
121
122 os.Exit(0)
123}