/ job_apply_ai / __main__.py
__main__.py
1 """ 2 Main entry point for the Job Application AI Agent. 3 4 This module provides a command-line interface to run different components 5 of the Job Application AI Agent. 6 """ 7 8 import argparse 9 import logging 10 import sys 11 import os 12 from datetime import datetime 13 14 # Configure logging 15 logging.basicConfig( 16 level=logging.INFO, 17 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' 18 ) 19 logger = logging.getLogger(__name__) 20 21 def main(): 22 """Main entry point for the application.""" 23 parser = argparse.ArgumentParser(description='Job Application AI Agent') 24 25 # Add subparsers for different commands 26 subparsers = parser.add_subparsers(dest='command', help='Command to run') 27 28 # Web UI command 29 web_parser = subparsers.add_parser('web', help='Start the web interface') 30 web_parser.add_argument('--host', default='0.0.0.0', help='Host to bind to') 31 web_parser.add_argument('--port', type=int, default=5000, help='Port to bind to') 32 web_parser.add_argument('--debug', action='store_true', help='Run in debug mode') 33 34 # Scraper command 35 scraper_parser = subparsers.add_parser('scrape', help='Scrape job listings') 36 scraper_parser.add_argument('--keyword', required=True, help='Job title or keyword to search for') 37 scraper_parser.add_argument('--location', required=True, help='Location to search in') 38 scraper_parser.add_argument('--output', help='Output file path (Excel)') 39 scraper_parser.add_argument('--max-jobs', type=int, default=10, help='Maximum number of jobs to scrape') 40 41 # CV modifier command 42 cv_parser = subparsers.add_parser('tailor', help='Tailor CV for a job') 43 cv_parser.add_argument('--cv', required=True, help='Path to CV template (.docx)') 44 cv_parser.add_argument('--job', help='Path to job description file (text)') 45 cv_parser.add_argument('--jobs-file', help='Path to Excel file with multiple job listings') 46 cv_parser.add_argument('--output-dir', help='Directory to save the tailored CVs') 47 cv_parser.add_argument('--output', help='Output file path for single job (.docx)') 48 49 # Batch processing command 50 batch_parser = subparsers.add_parser('batch', help='Process multiple jobs and generate CVs') 51 batch_parser.add_argument('--cv', required=True, help='Path to CV template (.docx)') 52 batch_parser.add_argument('--jobs-file', required=True, help='Path to Excel file with job listings') 53 batch_parser.add_argument('--output-dir', help='Directory to save the tailored CVs') 54 55 # Parse arguments 56 args = parser.parse_args() 57 58 if args.command == 'web': 59 # Import here to avoid circular imports 60 from job_apply_ai.ui.app import app 61 app.run(host=args.host, port=args.port, debug=args.debug) 62 63 elif args.command == 'scrape': 64 from job_apply_ai.scraper.linkedin import LinkedInScraper 65 66 scraper = LinkedInScraper(headless=True) 67 jobs = scraper.scrape_job_listings(args.keyword, args.location, max_jobs=args.max_jobs) 68 69 if jobs: 70 output_file = args.output 71 if not output_file: 72 # Save to the jobs output directory 73 output_dir = os.path.join(os.getcwd(), "job_apply_ai", "outputs", "jobs") 74 os.makedirs(output_dir, exist_ok=True) 75 76 today_date = datetime.today().strftime("%Y-%m-%d") 77 output_file = os.path.join(output_dir, f"linkedin_jobs_{today_date}.xlsx") 78 79 filename = scraper.save_jobs_to_excel(jobs, output_file) 80 logger.info(f"Jobs saved to {filename}") 81 82 # Fetch job descriptions 83 logger.info("Fetching job descriptions...") 84 for i, job in enumerate(jobs): 85 logger.info(f"Fetching description for job {i+1}/{len(jobs)}: {job['title']}") 86 title, company, description = scraper.fetch_job_description(job['link']) 87 jobs[i]['description'] = description 88 89 # Save updated jobs with descriptions 90 scraper.save_jobs_to_excel(jobs, output_file) 91 logger.info(f"Updated jobs with descriptions saved to {filename}") 92 else: 93 logger.warning("No jobs found") 94 95 elif args.command == 'tailor': 96 from job_apply_ai.cv_modifier.cv_analyzer import CVAnalyzer, CVModifier, batch_process_jobs 97 98 # Check if we're processing a single job or multiple jobs 99 if args.jobs_file: 100 # Process multiple jobs 101 output_dir = args.output_dir or os.path.join(os.getcwd(), "job_apply_ai", "outputs", "cvs") 102 generated_cvs = batch_process_jobs(args.jobs_file, args.cv, output_dir) 103 104 if generated_cvs: 105 logger.info(f"Generated {len(generated_cvs)} tailored CVs:") 106 for cv_path in generated_cvs: 107 logger.info(f" - {cv_path}") 108 else: 109 logger.warning("Failed to generate any CVs") 110 111 elif args.job: 112 # Process a single job 113 # Read job description 114 try: 115 with open(args.job, 'r') as f: 116 job_description = f.read() 117 except Exception as e: 118 logger.error(f"Error reading job description: {str(e)}") 119 sys.exit(1) 120 121 # Analyze job description 122 analyzer = CVAnalyzer() 123 matched_skills, matched_requirements, matched_categories = analyzer.extract_skills_from_description(job_description) 124 125 logger.info(f"Found {len(matched_skills)} matching skills") 126 for category, skills in matched_categories.items(): 127 logger.info(f"{category}: {', '.join(skills)}") 128 129 # Modify CV 130 try: 131 modifier = CVModifier(args.cv) 132 133 if modifier.update_skills_section(matched_categories): 134 # Determine output path 135 if args.output: 136 output_path = args.output 137 else: 138 output_dir = args.output_dir or os.path.join(os.getcwd(), "job_apply_ai", "outputs", "cvs") 139 os.makedirs(output_dir, exist_ok=True) 140 today_date = datetime.today().strftime("%Y-%m-%d") 141 output_path = os.path.join(output_dir, f"Tailored_CV_{today_date}.docx") 142 143 if modifier.save_modified_cv(output_path): 144 logger.info(f"Tailored CV saved to {output_path}") 145 else: 146 logger.error("Failed to save tailored CV") 147 else: 148 logger.error("Failed to update skills section") 149 except Exception as e: 150 logger.error(f"Error tailoring CV: {str(e)}") 151 sys.exit(1) 152 153 else: 154 logger.error("Either --job or --jobs-file must be specified") 155 sys.exit(1) 156 157 elif args.command == 'batch': 158 from job_apply_ai.cv_modifier.cv_analyzer import batch_process_jobs 159 160 output_dir = args.output_dir or os.path.join(os.getcwd(), "job_apply_ai", "outputs", "cvs") 161 generated_cvs = batch_process_jobs(args.jobs_file, args.cv, output_dir) 162 163 if generated_cvs: 164 logger.info(f"Generated {len(generated_cvs)} tailored CVs:") 165 for cv_path in generated_cvs: 166 logger.info(f" - {cv_path}") 167 else: 168 logger.warning("Failed to generate any CVs") 169 170 else: 171 parser.print_help() 172 sys.exit(1) 173 174 if __name__ == '__main__': 175 main()