Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:57:28 +08:00
commit e063391898
27 changed files with 3055 additions and 0 deletions

140
hooks/scripts/notification.py Executable file
View File

@@ -0,0 +1,140 @@
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "python-dotenv",
# ]
# ///
import argparse
import json
import os
import random
import subprocess
import sys
from pathlib import Path
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
pass # dotenv is optional
def get_tts_script_path():
"""
Determine which TTS script to use based on available API keys.
Priority order: ElevenLabs > OpenAI > pyttsx3
"""
# Get current script directory and construct utils/tts path
script_dir = Path(__file__).parent
tts_dir = script_dir / "utils" / "tts"
# Check for ElevenLabs API key (highest priority)
if os.getenv("ELEVENLABS_API_KEY"):
elevenlabs_script = tts_dir / "elevenlabs_tts.py"
if elevenlabs_script.exists():
return str(elevenlabs_script)
# Check for OpenAI API key (second priority)
if os.getenv("OPENAI_API_KEY"):
openai_script = tts_dir / "openai_tts.py"
if openai_script.exists():
return str(openai_script)
# Fall back to pyttsx3 (no API key required)
pyttsx3_script = tts_dir / "pyttsx3_tts.py"
if pyttsx3_script.exists():
return str(pyttsx3_script)
return None
def announce_notification():
"""Announce that the agent needs user input."""
try:
tts_script = get_tts_script_path()
if not tts_script:
return # No TTS scripts available
# Get engineer name if available
engineer_name = os.getenv("ENGINEER_NAME", "").strip()
# Create notification message with 30% chance to include name
if engineer_name and random.random() < 0.3:
notification_message = f"{engineer_name}, your agent needs your input"
else:
notification_message = "Your agent needs your input"
# Call the TTS script with the notification message
subprocess.run(
["uv", "run", tts_script, notification_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(
"--notify", action="store_true", help="Enable TTS notifications"
)
args = parser.parse_args()
# Read JSON input from stdin
input_data = json.loads(sys.stdin.read())
# Ensure log directory exists
import os
log_dir = os.path.join(os.getcwd(), "logs")
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, "notification.json")
# Read existing log data or initialize empty list
if os.path.exists(log_file):
with open(log_file) 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_file, "w") as f:
json.dump(log_data, f, indent=2)
# Announce notification via TTS only if --notify flag is set
# Skip TTS for the generic "Claude is waiting for your input" message
if (
args.notify
and input_data.get("message") != "Claude is waiting for your input"
):
announce_notification()
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()