auto-update-daily-20260202
  1package main
  2
  3import (
  4	"fmt"
  5	"os"
  6	"path/filepath"
  7	"strconv"
  8	"strings"
  9	"time"
 10
 11	"github.com/vdemeester/home/tools/claude-hooks/internal/paths"
 12)
 13
 14const (
 15	debounceDuration = 2 * time.Second
 16)
 17
 18func getLockfile() string {
 19	return filepath.Join(os.TempDir(), "claude-session-start.lock")
 20}
 21
 22// shouldDebounce checks if we're within the debounce window
 23func shouldDebounce() bool {
 24	lockfile := getLockfile()
 25
 26	data, err := os.ReadFile(lockfile)
 27	if err == nil {
 28		lockTime, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64)
 29		if err == nil {
 30			now := time.Now().UnixMilli()
 31			if now-lockTime < debounceDuration.Milliseconds() {
 32				return true
 33			}
 34		}
 35	}
 36
 37	// Update lockfile with current timestamp
 38	now := time.Now().UnixMilli()
 39	if err := os.WriteFile(lockfile, []byte(fmt.Sprintf("%d", now)), 0644); err != nil {
 40		// Ignore write errors
 41	}
 42
 43	return false
 44}
 45
 46// isSubagentSession checks if this is a subagent session
 47func isSubagentSession() bool {
 48	claudeProjectDir := os.Getenv("CLAUDE_PROJECT_DIR")
 49	if strings.Contains(claudeProjectDir, "/.claude/agents/") {
 50		return true
 51	}
 52	if os.Getenv("CLAUDE_AGENT_TYPE") != "" {
 53		return true
 54	}
 55	return false
 56}
 57
 58// setTerminalTitle sets the terminal tab title using ANSI escape codes
 59func setTerminalTitle(title string) {
 60	fmt.Fprintf(os.Stderr, "\x1b]0;%s\x07", title)
 61	fmt.Fprintf(os.Stderr, "\x1b]2;%s\x07", title)
 62	fmt.Fprintf(os.Stderr, "\x1b]30;%s\x07", title)
 63}
 64
 65// logSessionStart logs the session start to history
 66func logSessionStart() error {
 67	timestamp := paths.GetTimestamp()
 68	yearMonth := timestamp[:7] // YYYY-MM
 69
 70	logDir := filepath.Join(paths.HistoryDir(), "sessions", yearMonth)
 71	if err := os.MkdirAll(logDir, 0755); err != nil {
 72		return err
 73	}
 74
 75	logEntry := fmt.Sprintf("%s - Session started\n", time.Now().Format(time.RFC3339))
 76	logFile := filepath.Join(logDir, fmt.Sprintf("%s_session-log.txt", timestamp[:10]))
 77
 78	f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
 79	if err != nil {
 80		return err
 81	}
 82	defer f.Close()
 83
 84	_, err = f.WriteString(logEntry)
 85	return err
 86}
 87
 88// loadCoreSkill outputs the CORE skill content so Claude receives it at session start
 89func loadCoreSkill() error {
 90	homeDir, err := os.UserHomeDir()
 91	if err != nil {
 92		return err
 93	}
 94
 95	coreSkillPath := filepath.Join(homeDir, ".config/claude/skills/CORE/SKILL.md")
 96	content, err := os.ReadFile(coreSkillPath)
 97	if err != nil {
 98		return err
 99	}
100
101	// Output to stdout so Claude receives it
102	fmt.Println("\n<!-- CORE Skill Auto-Loaded at Session Start -->")
103	fmt.Println(string(content))
104	fmt.Println("<!-- End CORE Skill -->")
105
106	return nil
107}
108
109func main() {
110	// Check if this is a subagent session
111	if isSubagentSession() {
112		fmt.Fprintln(os.Stderr, "🤖 Subagent session detected - skipping session initialization")
113		os.Exit(0)
114	}
115
116	// Check debounce to prevent duplicate notifications
117	if shouldDebounce() {
118		fmt.Fprintln(os.Stderr, "🔇 Debouncing duplicate SessionStart event")
119		os.Exit(0)
120	}
121
122	// Set initial tab title
123	tabTitle := "Claude Ready"
124	setTerminalTitle(tabTitle)
125	fmt.Fprintf(os.Stderr, "📍 Session initialized: \"%s\"\n", tabTitle)
126
127	// Load CORE skill at session start
128	if err := loadCoreSkill(); err != nil {
129		// Warn but don't break session start
130		fmt.Fprintf(os.Stderr, "[initialize-session] Warning: Could not load CORE skill: %v\n", err)
131	}
132
133	// Ring terminal bell to notify user (works with kitty bell_on_tab)
134	fmt.Fprint(os.Stderr, "\a")
135
136	// Log session start to history (silent failure)
137	if err := logSessionStart(); err != nil {
138		// Don't break session start for logging issues
139		fmt.Fprintf(os.Stderr, "[initialize-session] Warning: Could not log session start: %v\n", err)
140	}
141
142	os.Exit(0)
143}