diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f4ba989 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,59 @@ +# Changelog + +All notable changes to the Receipt Printer Task Manager project will be documented in this file. + +## [Unreleased] - 2025-01-09 + +### Added +- **Setup Validation Script** (`setup_validation.py`) + - Comprehensive dependency checking + - Environment variable validation + - Database connectivity testing + - Automatic `.env` template generation + - Clear setup guidance and error messages + +### Fixed +- **Database Initialization**: Fixed missing `_create_tables()` call in TaskDatabase constructor +- **Missing Dependencies**: Added all required dependencies to `requirements.txt` + - `reportlab` for PDF generation + - `Pillow` for image processing + - `python-escpos` for thermal printer support + - `imgkit` and `selenium` for HTML to image conversion + - `pdf2image` for PDF to image conversion + +### Improved +- **Enhanced CLI Experience** in `main.py` + - Better user prompts and examples + - Input validation and error handling + - Interactive confirmation for outputs and printing + - Graceful error handling with helpful messages + - Proper exit codes for scripting + +- **Better Error Handling** in `agent.py` + - Database initialization error handling + - Individual task save error handling + - More informative error messages + +- **Updated Documentation** + - Improved installation instructions + - Added dependency information + - Updated usage examples with setup validation + +### Technical Improvements +- Added proper exception handling throughout the codebase +- Improved user experience with better prompts and feedback +- Added validation for essential requirements before execution +- Enhanced error messages with actionable suggestions + +### Breaking Changes +None - all changes are backward compatible. + +### Migration Guide +1. Run `pip install -r requirements.txt` to install new dependencies +2. Run `python setup_validation.py` to validate your setup +3. Follow any recommendations from the validation script + +--- + +## Previous Versions +This changelog starts from the current improvements. Previous version history was not tracked. \ No newline at end of file diff --git a/README.md b/README.md index 14dbb54..4b918c1 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,11 @@ cd receipt-printer-tasks # Install dependencies pip install -r requirements.txt -# Set up environment variables -cp .env.example .env +# Validate setup and create .env template +python setup_validation.py + # Edit .env with your API keys +# (A template will be created automatically) ``` ## Configuration @@ -35,9 +37,9 @@ Required environment variables: ## Usage -### Extract tasks from Gmail +### Validate setup (recommended first step) ```bash -python agent.py +python setup_validation.py ``` ### Create a task from text @@ -45,6 +47,11 @@ python agent.py python main.py ``` +### Extract tasks from Gmail +```bash +python agent.py +``` + ### Use Arcade tools ```bash python tools.py @@ -58,9 +65,28 @@ python setup_database.py ## Requirements - Python 3.8+ -- Thermal receipt printer (USB) +- Thermal receipt printer (USB) - optional - API keys for OpenAI and Arcade.dev +### Dependencies + +**Required:** +- `openai` - AI task processing +- `python-dotenv` - Environment configuration +- `pydantic` - Data validation +- `agents` & `arcadepy` - AI agent framework +- `libsql-experimental` - Database connectivity + +**Optional (for enhanced features):** +- `reportlab` - PDF generation +- `Pillow` - Image processing +- `python-escpos` - Thermal printer support +- `imgkit` + `wkhtmltopdf` - HTML to image conversion +- `selenium` - Web-based image generation +- `pdf2image` - PDF to image conversion + +The setup validation script will check all dependencies and provide installation guidance. + ## License MIT License - see LICENSE file for details. \ No newline at end of file diff --git a/agent.py b/agent.py index dd10ea5..5c4d1f2 100644 --- a/agent.py +++ b/agent.py @@ -88,7 +88,13 @@ async def main(): print("=" * 50) # Initialize database - db = TaskDatabase() + try: + db = TaskDatabase() + print("šŸ’¾ Database initialized successfully") + except Exception as e: + print(f"āŒ Database initialization failed: {e}") + print("šŸ’” Run 'python setup_validation.py' to check your setup") + return try: # Get user email from environment or ask @@ -132,14 +138,18 @@ async def main(): continue # Add new task to database - db_task = TaskRecord( - name=task.name, - priority=task.priority, - due_date=task.due_date, - created_at=datetime.datetime.now().isoformat(), - ) - db.add_task(db_task) - new_tasks.append(task) + try: + db_task = TaskRecord( + name=task.name, + priority=task.priority, + due_date=task.due_date, + created_at=datetime.datetime.now().isoformat(), + ) + db.add_task(db_task) + new_tasks.append(task) + except Exception as e: + print(f" āš ļø Failed to save task '{task.name}': {e}") + continue # Print summary of database operations if new_tasks: diff --git a/main.py b/main.py index 56952cc..66468fa 100644 --- a/main.py +++ b/main.py @@ -2,6 +2,7 @@ """Simple CLI for AI-powered task extraction and printing.""" import os +import sys from dotenv import load_dotenv from src.task_card_generator import ( create_task_image, @@ -16,67 +17,161 @@ load_dotenv() +def validate_setup(): + """Quick validation of essential requirements.""" + missing_requirements = [] + + # Check OpenAI API key + if not os.getenv("OPENAI_API_KEY"): + missing_requirements.append("OPENAI_API_KEY environment variable") + + # Check if we can import essential modules + try: + from src.task_card_generator import get_task_from_ai + except ImportError as e: + missing_requirements.append(f"Task generator module: {e}") + + if missing_requirements: + print("āŒ Setup validation failed:") + for req in missing_requirements: + print(f" - Missing: {req}") + print("\nšŸ’” Run 'python setup_validation.py' for detailed setup check") + return False + + return True + +def get_user_input(): + """Get task description from user with better prompts.""" + print("\nDescribe the task you need to complete:") + print("šŸ’” Examples:") + print(" - 'Review the quarterly budget report'") + print(" - 'Call dentist to schedule appointment'") + print(" - 'Prepare presentation for Monday meeting'") + print() + + while True: + task_description = input(">> ").strip() + + if not task_description: + print("āš ļø Please enter a task description (or 'quit' to exit)") + continue + + if task_description.lower() in ['quit', 'exit', 'q']: + return None + + if len(task_description) < 3: + print("āš ļø Task description too short. Please be more specific.") + continue + + return task_description + def main(): """Main CLI entry point for task generation.""" print("=" * 50) - print("AI TASK GENERATOR") + print("šŸŽÆ AI TASK GENERATOR") print("Turn your thoughts into actionable tasks") print("=" * 50) - # Get user task description - print("\nDescribe the task you need to complete:") - task_description = input(">> ") - - if not task_description.strip(): - print("ERROR: Please enter a valid task description!") - return + # Validate setup + if not validate_setup(): + return 1 - # Get AI response - print("\nšŸ¤– Processing with AI...") - ai_response = get_task_from_ai(task_description) - - if not ai_response or ai_response.startswith("Error"): - print(f"ERROR: {ai_response or 'Failed to get response'}") - return - - # Parse AI response - task_data = parse_ai_response(ai_response) - - print(f"\nāœ… Generated Task: {task_data['title']}") - print(f"šŸ“Œ Priority: {task_data['priority']}") - print(f"šŸ“… Due: {task_data.get('due_date', 'Today')}") + # Get user task description + task_description = get_user_input() + if not task_description: + print("šŸ‘‹ Goodbye!") + return 0 + + try: + # Get AI response + print("\nšŸ¤– Processing with AI...") + ai_response = get_task_from_ai(task_description) + + if not ai_response or ai_response.startswith("Error"): + print(f"āŒ AI processing failed: {ai_response or 'No response received'}") + print("šŸ’” Check your OpenAI API key and internet connection") + return 1 + + # Parse AI response + task_data = parse_ai_response(ai_response) + + print(f"\nāœ… Generated Task: {task_data['title']}") + print(f"šŸ“Œ Priority: {task_data['priority']}") + print(f"šŸ“… Due: {task_data.get('due_date', 'Today')}") + + # Ask user if they want to proceed with outputs + print(f"\nšŸ“„ Create outputs? (PDF, image, print) [Y/n]: ", end="") + create_outputs = input().strip().lower() + + if create_outputs in ['n', 'no']: + print("āœ… Task created successfully (no outputs generated)") + return 0 - # Create outputs - print("\nšŸ“„ Creating outputs...") - - # Create PDF for viewing - pdf_path = create_task_pdf(task_data) - if pdf_path: - print(f" PDF saved: {pdf_path}") - - # Create image for printing - image_path = create_task_html_image(task_data) - if not image_path: - # Fallback to PIL method - image_path = create_task_image(task_data) - - if image_path: - print(f" Image saved: {image_path}") + # Create outputs + print("\nšŸ“„ Creating outputs...") + outputs_created = [] - # Print if printer is available + # Create PDF for viewing try: - print("\nšŸ–Øļø Sending to printer...") - print_to_thermal_printer(image_path) - print(" āœ… Printed successfully!") + pdf_path = create_task_pdf(task_data) + if pdf_path: + print(f" āœ… PDF saved: {pdf_path}") + outputs_created.append("PDF") + else: + print(f" āš ļø PDF creation skipped (reportlab not available)") except Exception as e: - print(f" āš ļø Printing failed: {e}") - else: - print(" āŒ Could not create image") + print(f" āŒ PDF creation failed: {e}") - print("\n" + "=" * 50) - print("Task generation complete!") - print("=" * 50) + # Create image for printing + image_path = None + try: + image_path = create_task_html_image(task_data) + if not image_path: + # Fallback to PIL method + image_path = create_task_image(task_data) + + if image_path: + print(f" āœ… Image saved: {image_path}") + outputs_created.append("Image") + else: + print(f" āš ļø Image creation failed (check image dependencies)") + except Exception as e: + print(f" āŒ Image creation failed: {e}") + + # Print if printer is available and image was created + if image_path: + print(f"\nšŸ–Øļø Print to thermal printer? [y/N]: ", end="") + should_print = input().strip().lower() + + if should_print in ['y', 'yes']: + try: + print(" šŸ–Øļø Sending to printer...") + print_to_thermal_printer(image_path) + print(" āœ… Printed successfully!") + outputs_created.append("Print") + except Exception as e: + print(f" āŒ Printing failed: {e}") + print(" šŸ’” Make sure your thermal printer is connected and configured") + + # Summary + print(f"\n" + "=" * 50) + if outputs_created: + print(f"šŸŽ‰ Task generation complete! Created: {', '.join(outputs_created)}") + else: + print("āœ… Task generated (no outputs created)") + print("=" * 50) + + return 0 + + except KeyboardInterrupt: + print("\n\nšŸ‘‹ Operation cancelled by user") + return 0 + except Exception as e: + print(f"\nāŒ Unexpected error: {e}") + print("šŸ’” Run 'python setup_validation.py' to check your setup") + return 1 if __name__ == "__main__": - main() \ No newline at end of file + exit_code = main() + sys.exit(exit_code) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 507bea7..246aae0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,10 @@ arcadepy libsql-experimental openai python-dotenv -pydantic \ No newline at end of file +pydantic +reportlab +pdf2image +Pillow +python-escpos +imgkit +selenium \ No newline at end of file diff --git a/setup_validation.py b/setup_validation.py new file mode 100644 index 0000000..4c04d49 --- /dev/null +++ b/setup_validation.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +"""Setup validation script to check dependencies and configuration.""" + +import os +import sys +from typing import Dict, List, Tuple + +def check_python_version() -> Tuple[bool, str]: + """Check if Python version is compatible.""" + version = sys.version_info + if version.major == 3 and version.minor >= 8: + return True, f"āœ… Python {version.major}.{version.minor}.{version.micro}" + return False, f"āŒ Python {version.major}.{version.minor}.{version.micro} (requires 3.8+)" + +def check_dependencies() -> Dict[str, Tuple[bool, str]]: + """Check if required dependencies are installed.""" + dependencies = { + "openai": "OpenAI API client", + "dotenv": "Environment variable loading", + "pydantic": "Data validation", + "agents": "AI agent framework", + "arcadepy": "Arcade API client", + "libsql_experimental": "Database connectivity", + "reportlab": "PDF generation", + "PIL": "Image processing (Pillow)", + "escpos": "Thermal printer support", + } + + optional_dependencies = { + "imgkit": "HTML to image conversion", + "selenium": "Web-based image generation", + "pdf2image": "PDF to image conversion", + } + + results = {} + + # Check required dependencies + for package, description in dependencies.items(): + try: + if package == "dotenv": + import dotenv + elif package == "PIL": + import PIL + else: + __import__(package) + results[package] = (True, f"āœ… {description}") + except ImportError: + results[package] = (False, f"āŒ {description} - Missing") + + # Check optional dependencies + for package, description in optional_dependencies.items(): + try: + __import__(package) + results[package] = (True, f"āœ… {description} (optional)") + except ImportError: + results[package] = (False, f"āš ļø {description} (optional) - Missing") + + return results + +def check_environment_variables() -> Dict[str, Tuple[bool, str]]: + """Check if required environment variables are set.""" + from dotenv import load_dotenv + load_dotenv() + + required_vars = { + "OPENAI_API_KEY": "OpenAI API access", + "ARCADE_API_KEY": "Arcade.dev API access", + } + + optional_vars = { + "TURSO_DATABASE_URL": "Remote database URL", + "TURSO_AUTH_TOKEN": "Remote database authentication", + "ARCADE_USER_ID": "Arcade user identification", + } + + results = {} + + # Check required variables + for var, description in required_vars.items(): + value = os.getenv(var) + if value and len(value.strip()) > 0: + # Mask the key for security + masked = value[:8] + "..." if len(value) > 8 else "***" + results[var] = (True, f"āœ… {description} ({masked})") + else: + results[var] = (False, f"āŒ {description} - Not set") + + # Check optional variables + for var, description in optional_vars.items(): + value = os.getenv(var) + if value and len(value.strip()) > 0: + masked = value[:8] + "..." if len(value) > 8 else "***" + results[var] = (True, f"āœ… {description} ({masked})") + else: + results[var] = (False, f"āš ļø {description} (optional) - Not set") + + return results + +def check_database_connectivity() -> Tuple[bool, str]: + """Test database connection and table creation.""" + try: + from src.database.task_db import TaskDatabase + + # Test database initialization + db = TaskDatabase() + + # Test basic operations + recent_tasks = db.get_recent_tasks(limit=1) + db.close() + + return True, "āœ… Database connection successful" + except Exception as e: + return False, f"āŒ Database connection failed: {str(e)}" + +def check_thermal_printer() -> Tuple[bool, str]: + """Check if thermal printer is available.""" + try: + from escpos.printer import Win32Raw + # This is just a basic import test - actual printer detection would need specific hardware + return True, "āœ… Thermal printer library available" + except ImportError: + return False, "āŒ Thermal printer library not available" + except Exception as e: + return False, f"āš ļø Thermal printer library available but may have issues: {str(e)}" + +def generate_env_template(): + """Generate a .env template file if it doesn't exist.""" + env_template = """# API Keys (Required) +OPENAI_API_KEY=your_openai_api_key_here +ARCADE_API_KEY=your_arcade_api_key_here + +# Database Configuration (Optional - uses local SQLite if not set) +TURSO_DATABASE_URL=your_turso_database_url_here +TURSO_AUTH_TOKEN=your_turso_auth_token_here + +# User Configuration (Optional) +ARCADE_USER_ID=your_email@example.com +""" + + if not os.path.exists('.env'): + with open('.env', 'w') as f: + f.write(env_template) + return "āœ… Created .env template file" + else: + return "āš ļø .env file already exists" + +def main(): + """Run complete setup validation.""" + print("=" * 60) + print("šŸ”§ RECEIPT PRINTER TASK MANAGER - SETUP VALIDATION") + print("=" * 60) + + all_good = True + + # Check Python version + python_ok, python_msg = check_python_version() + print(f"\nšŸ“ PYTHON VERSION") + print(f" {python_msg}") + if not python_ok: + all_good = False + + # Check dependencies + print(f"\nšŸ“¦ DEPENDENCIES") + deps = check_dependencies() + required_missing = [] + optional_missing = [] + + for package, (status, message) in deps.items(): + print(f" {message}") + if not status: + if "optional" in message: + optional_missing.append(package) + else: + required_missing.append(package) + all_good = False + + # Check environment variables + print(f"\nšŸ”‘ ENVIRONMENT VARIABLES") + env_vars = check_environment_variables() + for var, (status, message) in env_vars.items(): + print(f" {message}") + if not status and "optional" not in message: + all_good = False + + # Generate .env template if needed + env_template_msg = generate_env_template() + print(f" {env_template_msg}") + + # Check database + print(f"\nšŸ’¾ DATABASE") + db_ok, db_msg = check_database_connectivity() + print(f" {db_msg}") + if not db_ok: + all_good = False + + # Check printer + print(f"\nšŸ–Øļø THERMAL PRINTER") + printer_ok, printer_msg = check_thermal_printer() + print(f" {printer_msg}") + + # Summary + print(f"\n" + "=" * 60) + if all_good: + print("šŸŽ‰ SETUP VALIDATION PASSED!") + print(" Your system is ready to run the Receipt Printer Task Manager.") + print("\nšŸ“‹ NEXT STEPS:") + print(" 1. Run 'python main.py' to create tasks manually") + print(" 2. Run 'python agent.py' to extract tasks from Gmail") + print(" 3. Run 'python tools.py' to use Arcade tools") + else: + print("āš ļø SETUP VALIDATION FAILED!") + print(" Please fix the issues above before running the application.") + + if required_missing: + print(f"\nšŸ“„ INSTALL MISSING DEPENDENCIES:") + print(f" pip install {' '.join(required_missing)}") + + if optional_missing: + print(f"\nšŸ“„ OPTIONAL DEPENDENCIES (for enhanced features):") + print(f" pip install {' '.join(optional_missing)}") + + print("=" * 60) + + return all_good + +if __name__ == "__main__": + success = main() + sys.exit(0 if success else 1) \ No newline at end of file diff --git a/src/database/task_db.py b/src/database/task_db.py index ea00450..d622da8 100644 --- a/src/database/task_db.py +++ b/src/database/task_db.py @@ -46,6 +46,9 @@ def __init__(self, db_url: Optional[str] = None, auth_token: Optional[str] = Non else: # Local SQLite database self.conn = libsql.connect("tasks.db") + + # Initialize database tables + self._create_tables() def _create_tables(self): """Create tasks table with embeddings if it doesn't exist."""