fedora-csb-system-manager
  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}