Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:58:45 +08:00
commit 424d349cd7
13 changed files with 1563 additions and 0 deletions

179
hooks/stop.py Executable file
View File

@@ -0,0 +1,179 @@
#!/usr/bin/env python3
import argparse
import json
import os
import sys
import random
import subprocess
from pathlib import Path
from datetime import datetime
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass # dotenv is optional
def get_completion_messages():
"""Return list of friendly completion messages."""
return [
"Work complete!",
"All done!",
"Task finished!",
"Job complete!",
"Ready for next task!"
]
def get_tts_script_path():
"""
Determine which TTS script to use - only pyttsx3 is available.
"""
# Get current script directory and construct utils/tts path
script_dir = Path(__file__).parent
tts_dir = script_dir / "utils" / "tts"
# Use pyttsx3 (no API key required)
pyttsx3_script = tts_dir / "pyttsx3_tts.py"
if pyttsx3_script.exists():
return str(pyttsx3_script)
return None
def get_llm_completion_message():
"""
Generate completion message using Anthropic or fallback to random message.
Returns:
str: Generated or fallback completion message
"""
# Get current script directory and construct utils/llm path
script_dir = Path(__file__).parent
llm_dir = script_dir / "utils" / "llm"
# Try Anthropic first
if os.getenv('__ANTHROPIC_API_KEY'):
anth_script = llm_dir / "anth.py"
if anth_script.exists():
try:
result = subprocess.run([
"python3", str(anth_script), "--completion"
],
capture_output=True,
text=True,
timeout=10
)
if result.returncode == 0 and result.stdout.strip():
return result.stdout.strip()
except (subprocess.TimeoutExpired, subprocess.SubprocessError):
pass
# Fallback to random predefined message
messages = get_completion_messages()
return random.choice(messages)
def announce_completion():
"""Announce completion using the best available TTS service."""
try:
tts_script = get_tts_script_path()
if not tts_script:
return # No TTS scripts available
# Get completion message (LLM-generated or fallback)
completion_message = get_llm_completion_message()
# Call the TTS script with the completion message
subprocess.run([
"python3", tts_script, completion_message
],
capture_output=True, # Suppress output
timeout=10 # 10-second timeout
)
except (subprocess.TimeoutExpired, subprocess.SubprocessError, FileNotFoundError):
# Fail silently if TTS encounters issues
pass
except Exception:
# Fail silently for any other errors
pass
def main():
try:
# Parse command line arguments
parser = argparse.ArgumentParser()
parser.add_argument('--chat', action='store_true', help='Copy transcript to chat.json')
parser.add_argument('--notify', action='store_true', help='Enable TTS completion announcement')
args = parser.parse_args()
# Read JSON input from stdin
input_data = json.load(sys.stdin)
# Extract required fields
session_id = input_data.get("session_id", "")
stop_hook_active = input_data.get("stop_hook_active", False)
# Ensure log directory exists
log_dir = os.path.join(os.getcwd(), ".claude", "logs")
os.makedirs(log_dir, exist_ok=True)
log_path = os.path.join(log_dir, "stop.json")
# Read existing log data or initialize empty list
if os.path.exists(log_path):
with open(log_path, 'r') as f:
try:
log_data = json.load(f)
except (json.JSONDecodeError, ValueError):
log_data = []
else:
log_data = []
# Append new data
log_data.append(input_data)
# Write back to file with formatting
with open(log_path, 'w') as f:
json.dump(log_data, f, indent=2)
# Handle --chat switch
if args.chat and 'transcript_path' in input_data:
transcript_path = input_data['transcript_path']
if os.path.exists(transcript_path):
# Read .jsonl file and convert to JSON array
chat_data = []
try:
with open(transcript_path, 'r') as f:
for line in f:
line = line.strip()
if line:
try:
chat_data.append(json.loads(line))
except json.JSONDecodeError:
pass # Skip invalid lines
# Write to .claude/logs/chat.json
chat_file = os.path.join(log_dir, 'chat.json')
with open(chat_file, 'w') as f:
json.dump(chat_data, f, indent=2)
except Exception:
pass # Fail silently
# Announce completion via TTS (only if --notify flag is set)
if args.notify:
announce_completion()
sys.exit(0)
except json.JSONDecodeError:
# Handle JSON decode errors gracefully
sys.exit(0)
except Exception:
# Handle any other errors gracefully
sys.exit(0)
if __name__ == "__main__":
main()