/ 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()