1+ #!/usr/bin/env -S uv run --script
2+ # /// script
3+ # requires-python = ">=3.11"
4+ # dependencies = [
5+ # "python-dotenv",
6+ # ]
7+ # ///
8+
9+ import argparse
10+ import json
11+ import os
12+ import sys
13+ import subprocess
14+ from pathlib import Path
15+ from datetime import datetime
16+
17+ try :
18+ from dotenv import load_dotenv
19+ load_dotenv ()
20+ except ImportError :
21+ pass # dotenv is optional
22+
23+
24+ def log_session_start (input_data ):
25+ """Log session start event to logs directory."""
26+ # Ensure logs directory exists
27+ log_dir = Path ("logs" )
28+ log_dir .mkdir (parents = True , exist_ok = True )
29+ log_file = log_dir / 'session_start.json'
30+
31+ # Read existing log data or initialize empty list
32+ if log_file .exists ():
33+ with open (log_file , 'r' ) as f :
34+ try :
35+ log_data = json .load (f )
36+ except (json .JSONDecodeError , ValueError ):
37+ log_data = []
38+ else :
39+ log_data = []
40+
41+ # Append the entire input data
42+ log_data .append (input_data )
43+
44+ # Write back to file with formatting
45+ with open (log_file , 'w' ) as f :
46+ json .dump (log_data , f , indent = 2 )
47+
48+
49+ def get_git_status ():
50+ """Get current git status information."""
51+ try :
52+ # Get current branch
53+ branch_result = subprocess .run (
54+ ['git' , 'rev-parse' , '--abbrev-ref' , 'HEAD' ],
55+ capture_output = True ,
56+ text = True ,
57+ timeout = 5
58+ )
59+ current_branch = branch_result .stdout .strip () if branch_result .returncode == 0 else "unknown"
60+
61+ # Get uncommitted changes count
62+ status_result = subprocess .run (
63+ ['git' , 'status' , '--porcelain' ],
64+ capture_output = True ,
65+ text = True ,
66+ timeout = 5
67+ )
68+ if status_result .returncode == 0 :
69+ changes = status_result .stdout .strip ().split ('\n ' ) if status_result .stdout .strip () else []
70+ uncommitted_count = len (changes )
71+ else :
72+ uncommitted_count = 0
73+
74+ return current_branch , uncommitted_count
75+ except Exception :
76+ return None , None
77+
78+
79+ def get_recent_issues ():
80+ """Get recent GitHub issues if gh CLI is available."""
81+ try :
82+ # Check if gh is available
83+ gh_check = subprocess .run (['which' , 'gh' ], capture_output = True )
84+ if gh_check .returncode != 0 :
85+ return None
86+
87+ # Get recent open issues
88+ result = subprocess .run (
89+ ['gh' , 'issue' , 'list' , '--limit' , '5' , '--state' , 'open' ],
90+ capture_output = True ,
91+ text = True ,
92+ timeout = 10
93+ )
94+ if result .returncode == 0 and result .stdout .strip ():
95+ return result .stdout .strip ()
96+ except Exception :
97+ pass
98+ return None
99+
100+
101+ def load_development_context (source ):
102+ """Load relevant development context based on session source."""
103+ context_parts = []
104+
105+ # Add timestamp
106+ context_parts .append (f"Session started at: { datetime .now ().strftime ('%Y-%m-%d %H:%M:%S' )} " )
107+ context_parts .append (f"Session source: { source } " )
108+
109+ # Add git information
110+ branch , changes = get_git_status ()
111+ if branch :
112+ context_parts .append (f"Git branch: { branch } " )
113+ if changes > 0 :
114+ context_parts .append (f"Uncommitted changes: { changes } files" )
115+
116+ # Load project-specific context files if they exist
117+ context_files = [
118+ ".claude/CONTEXT.md" ,
119+ ".claude/TODO.md" ,
120+ "TODO.md" ,
121+ ".github/ISSUE_TEMPLATE.md"
122+ ]
123+
124+ for file_path in context_files :
125+ if Path (file_path ).exists ():
126+ try :
127+ with open (file_path , 'r' ) as f :
128+ content = f .read ().strip ()
129+ if content :
130+ context_parts .append (f"\n --- Content from { file_path } ---" )
131+ context_parts .append (content [:1000 ]) # Limit to first 1000 chars
132+ except Exception :
133+ pass
134+
135+ # Add recent issues if available
136+ issues = get_recent_issues ()
137+ if issues :
138+ context_parts .append ("\n --- Recent GitHub Issues ---" )
139+ context_parts .append (issues )
140+
141+ return "\n " .join (context_parts )
142+
143+
144+ def main ():
145+ try :
146+ # Parse command line arguments
147+ parser = argparse .ArgumentParser ()
148+ parser .add_argument ('--load-context' , action = 'store_true' ,
149+ help = 'Load development context at session start' )
150+ parser .add_argument ('--announce' , action = 'store_true' ,
151+ help = 'Announce session start via TTS' )
152+ args = parser .parse_args ()
153+
154+ # Read JSON input from stdin
155+ input_data = json .loads (sys .stdin .read ())
156+
157+ # Extract fields
158+ session_id = input_data .get ('session_id' , 'unknown' )
159+ source = input_data .get ('source' , 'unknown' ) # "startup", "resume", or "clear"
160+
161+ # Log the session start event
162+ log_session_start (input_data )
163+
164+ # Load development context if requested
165+ if args .load_context :
166+ context = load_development_context (source )
167+ if context :
168+ # Using JSON output to add context
169+ output = {
170+ "hookSpecificOutput" : {
171+ "hookEventName" : "SessionStart" ,
172+ "additionalContext" : context
173+ }
174+ }
175+ print (json .dumps (output ))
176+ sys .exit (0 )
177+
178+ # Announce session start if requested
179+ if args .announce :
180+ try :
181+ # Try to use TTS to announce session start
182+ script_dir = Path (__file__ ).parent
183+ tts_script = script_dir / "utils" / "tts" / "pyttsx3_tts.py"
184+
185+ if tts_script .exists ():
186+ messages = {
187+ "startup" : "Claude Code session started" ,
188+ "resume" : "Resuming previous session" ,
189+ "clear" : "Starting fresh session"
190+ }
191+ message = messages .get (source , "Session started" )
192+
193+ subprocess .run (
194+ ["uv" , "run" , str (tts_script ), message ],
195+ capture_output = True ,
196+ timeout = 5
197+ )
198+ except Exception :
199+ pass
200+
201+ # Success
202+ sys .exit (0 )
203+
204+ except json .JSONDecodeError :
205+ # Handle JSON decode errors gracefully
206+ sys .exit (0 )
207+ except Exception :
208+ # Handle any other errors gracefully
209+ sys .exit (0 )
210+
211+
212+ if __name__ == '__main__' :
213+ main ()
0 commit comments