TradingAgents/.claude/lib/first_run_warning.py

262 lines
8.0 KiB
Python

#!/usr/bin/env python3
"""
First-run warning system for autonomous-dev plugin.
Interactive warning system for opt-out consent on first /auto-implement run.
Features:
- Displays first-run warning about automatic git operations
- Prompts user for consent (Y/n, defaults to yes)
- Records user choice in state file
- Skips warning in non-interactive sessions
- Graceful error handling
Date: 2025-11-11
Issue: #61 (Enable Zero Manual Git Operations by Default)
Agent: implementer
See error-handling-patterns skill for exception hierarchy and error handling best practices.
Design Patterns:
See library-design-patterns skill for standardized design patterns.
"""
import os
import sys
from pathlib import Path
# Import user state manager (standard pattern from project libraries)
try:
from .user_state_manager import (
UserStateManager,
is_first_run,
DEFAULT_STATE_FILE
)
except ImportError:
# Direct script execution - add lib dir to path
lib_dir = Path(__file__).parent.resolve()
sys.path.insert(0, str(lib_dir))
from user_state_manager import (
UserStateManager,
is_first_run,
DEFAULT_STATE_FILE
)
# Exception hierarchy pattern from error-handling-patterns skill:
# BaseException -> Exception -> AutonomousDevError -> DomainError(BaseException) -> SpecificError
class FirstRunWarningError(Exception):
"""Exception raised for first-run warning errors."""
pass
def render_warning() -> str:
"""
Render first-run warning message.
Returns:
Formatted warning message with user prompt
"""
warning = """
╔══════════════════════════════════════════════════════════════╗
║ ║
║ 🚀 Zero Manual Git Operations (NEW DEFAULT) ║
║ ║
║ Automatic git operations enabled after /auto-implement: ║
║ ║
║ ✓ automatic commit with conventional commit message ║
║ ✓ automatic push to remote ║
║ ✓ automatic pull request creation ║
║ ║
║ HOW TO OPT OUT: ║
║ ║
║ Add to .env file: ║
║ AUTO_GIT_ENABLED=false ║
║ ║
║ Or disable specific operations: ║
║ AUTO_GIT_PUSH=false # Disable push ║
║ AUTO_GIT_PR=false # Disable PR creation ║
║ ║
║ See docs/GIT-AUTOMATION.md for details ║
║ ║
╚══════════════════════════════════════════════════════════════╝
Do you want to enable automatic git operations? (Y/n): """
return warning
def parse_user_input(user_input: str) -> bool:
"""
Parse user input for consent.
Accepts: 'yes', 'y', 'Y', 'YES', '' (empty = yes)
Rejects: 'no', 'n', 'N', 'NO'
Args:
user_input: User input string
Returns:
True if accepted, False if rejected
Raises:
FirstRunWarningError: If input is invalid
"""
# Strip whitespace
user_input = user_input.strip()
# Empty input defaults to yes
if not user_input:
return True
# Check for yes
if user_input.lower() in {'yes', 'y'}:
return True
# Check for no
if user_input.lower() in {'no', 'n'}:
return False
# Invalid input
raise FirstRunWarningError(
f"Invalid input: '{user_input}'. Please enter 'yes' or 'no' (or press Enter for yes)."
)
def is_interactive_session() -> bool:
"""
Detect if running in an interactive session.
Returns:
True if interactive, False otherwise
"""
# Check if in CI environment
if os.environ.get("CI"):
return False
# Check if stdin is a TTY
try:
return sys.stdin.isatty()
except Exception:
return False
def show_first_run_warning(
state_file: Path = DEFAULT_STATE_FILE,
max_retries: int = 3
) -> bool:
"""
Show first-run warning and prompt user for consent.
Args:
state_file: Path to state file
max_retries: Maximum number of retry attempts for invalid input
Returns:
True if user accepts, False if user rejects
Raises:
FirstRunWarningError: If max retries exceeded or interrupted
"""
# Skip in non-interactive sessions
if not is_interactive_session():
# Default to True (opt-out model)
record_user_choice(accepted=True, state_file=state_file)
return True
# Display warning (print to sys.stdout explicitly for tests)
warning = render_warning()
sys.stdout.write(warning)
sys.stdout.flush()
# Prompt for input with retries
retry_count = 0
while retry_count < max_retries:
try:
user_input = input()
accepted = parse_user_input(user_input)
# Record choice
record_user_choice(accepted=accepted, state_file=state_file)
return accepted
except FirstRunWarningError as e:
retry_count += 1
if retry_count >= max_retries:
raise FirstRunWarningError(
f"Maximum retries exceeded. Please run /auto-implement again and enter 'yes' or 'no'."
)
sys.stdout.write(f"\n{e}\n")
sys.stdout.write("Do you want to enable automatic git operations? (Y/n): ")
sys.stdout.flush()
except KeyboardInterrupt:
raise FirstRunWarningError("Interrupted by user")
except EOFError:
# End of input - default to yes
record_user_choice(accepted=True, state_file=state_file)
return True
# Should not reach here
raise FirstRunWarningError("Unexpected error in first-run warning")
def record_user_choice(accepted: bool, state_file: Path = DEFAULT_STATE_FILE) -> None:
"""
Record user choice in state file.
Args:
accepted: True if user accepted, False if rejected
state_file: Path to state file
Raises:
FirstRunWarningError: If recording fails
"""
try:
manager = UserStateManager(state_file)
manager.set_preference("auto_git_enabled", accepted)
manager.record_first_run_complete()
manager.save()
except Exception as e:
raise FirstRunWarningError(f"Failed to record user choice: {e}")
def should_show_warning(state_file: Path = DEFAULT_STATE_FILE) -> bool:
"""
Determine whether to show first-run warning.
Skips warning if:
- Not first run (user already made a choice)
- AUTO_GIT_ENABLED env var is set (user already configured)
- Non-interactive session (can't prompt for input)
Args:
state_file: Path to state file
Returns:
True if warning should be shown, False otherwise
"""
# Skip if env var is already set
if os.environ.get("AUTO_GIT_ENABLED") is not None:
return False
# Skip in non-interactive sessions
if not is_interactive_session():
return False
# Show if first run
return is_first_run(state_file)
if __name__ == "__main__":
# CLI test
try:
result = show_first_run_warning()
print(f"\nUser choice: {'Accepted' if result else 'Rejected'}")
except FirstRunWarningError as e:
print(f"Error: {e}")
sys.exit(1)