skillsync-cli / job_apply_ai /__main__.py
Mr-Haseeb786
Sanitized Production Build
56c7b6d
Raw
History Blame Contribute Delete
7.9 kB
"""
Main entry point for the Job Application AI Agent.
This module provides a command-line interface to run different components
of the Job Application AI Agent.
"""
import argparse
import logging
import sys
import os
from datetime import datetime
from dotenv import load_dotenv
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Load environment variables from project root .env if present.
load_dotenv(os.path.join(os.getcwd(), ".env"))
def main():
"""Main entry point for the application."""
parser = argparse.ArgumentParser(description='Job Application AI Agent')
# Add subparsers for different commands
subparsers = parser.add_subparsers(dest='command', help='Command to run')
# Web UI command
web_parser = subparsers.add_parser('web', help='Start the web interface')
web_parser.add_argument('--host', default='0.0.0.0', help='Host to bind to')
web_parser.add_argument('--port', type=int, default=5000, help='Port to bind to')
web_parser.add_argument('--debug', action='store_true', help='Run in debug mode')
# Scraper command
scraper_parser = subparsers.add_parser('scrape', help='Scrape job listings')
scraper_parser.add_argument('--keyword', required=True, help='Job title or keyword to search for')
scraper_parser.add_argument('--location', required=True, help='Location to search in')
scraper_parser.add_argument('--output', help='Output file path (Excel)')
scraper_parser.add_argument('--max-jobs', type=int, default=10, help='Maximum number of jobs to scrape')
# CV modifier command
cv_parser = subparsers.add_parser('tailor', help='Tailor CV for a job')
cv_parser.add_argument('--cv', required=True, help='Path to CV template (.docx)')
cv_parser.add_argument('--job', help='Path to job description file (text)')
cv_parser.add_argument('--jobs-file', help='Path to Excel file with multiple job listings')
cv_parser.add_argument('--output-dir', help='Directory to save the tailored CVs')
cv_parser.add_argument('--output', help='Output file path for single job (.docx)')
# Batch processing command
batch_parser = subparsers.add_parser('batch', help='Process multiple jobs and generate CVs')
batch_parser.add_argument('--cv', required=True, help='Path to CV template (.docx)')
batch_parser.add_argument('--jobs-file', required=True, help='Path to Excel file with job listings')
batch_parser.add_argument('--output-dir', help='Directory to save the tailored CVs')
# Parse arguments
args = parser.parse_args()
if args.command == 'web':
# Import here to avoid circular imports
from job_apply_ai.ui.app import app
app.run(host=args.host, port=args.port, debug=args.debug)
elif args.command == 'scrape':
from job_apply_ai.scraper.linkedin import LinkedInScraper
scraper = LinkedInScraper(headless=True)
jobs = scraper.scrape_job_listings(args.keyword, args.location, max_jobs=args.max_jobs)
if jobs:
output_file = args.output
if not output_file:
# Save to the jobs output directory
output_dir = os.path.join(os.getcwd(), "job_apply_ai", "outputs", "jobs")
os.makedirs(output_dir, exist_ok=True)
today_date = datetime.today().strftime("%Y-%m-%d")
output_file = os.path.join(output_dir, f"linkedin_jobs_{today_date}.xlsx")
filename = scraper.save_jobs_to_excel(jobs, output_file)
logger.info(f"Jobs saved to {filename}")
# Fetch job descriptions
logger.info("Fetching job descriptions...")
for i, job in enumerate(jobs):
logger.info(f"Fetching description for job {i+1}/{len(jobs)}: {job['title']}")
title, company, description = scraper.fetch_job_description(job['link'])
jobs[i]['description'] = description
# Save updated jobs with descriptions
scraper.save_jobs_to_excel(jobs, output_file)
logger.info(f"Updated jobs with descriptions saved to {filename}")
else:
logger.warning("No jobs found")
elif args.command == 'tailor':
from job_apply_ai.cv_modifier.cv_analyzer import CVAnalyzer, CVModifier, batch_process_jobs
# Check if we're processing a single job or multiple jobs
if args.jobs_file:
# Process multiple jobs
output_dir = args.output_dir or os.path.join(os.getcwd(), "job_apply_ai", "outputs", "cvs")
generated_cvs = batch_process_jobs(args.jobs_file, args.cv, output_dir)
if generated_cvs:
logger.info(f"Generated {len(generated_cvs)} tailored CVs:")
for cv_path in generated_cvs:
logger.info(f" - {cv_path}")
else:
logger.warning("Failed to generate any CVs")
elif args.job:
# Process a single job
# Read job description
try:
with open(args.job, 'r') as f:
job_description = f.read()
except Exception as e:
logger.error(f"Error reading job description: {str(e)}")
sys.exit(1)
# Analyze job description
analyzer = CVAnalyzer()
matched_skills, matched_requirements, matched_categories = analyzer.extract_skills_from_description(job_description)
logger.info(f"Found {len(matched_skills)} matching skills")
for category, skills in matched_categories.items():
logger.info(f"{category}: {', '.join(skills)}")
# Modify CV
try:
modifier = CVModifier(args.cv)
if modifier.update_skills_section(matched_categories):
# Determine output path
if args.output:
output_path = args.output
else:
output_dir = args.output_dir or os.path.join(os.getcwd(), "job_apply_ai", "outputs", "cvs")
os.makedirs(output_dir, exist_ok=True)
today_date = datetime.today().strftime("%Y-%m-%d")
output_path = os.path.join(output_dir, f"Tailored_CV_{today_date}.docx")
if modifier.save_modified_cv(output_path):
logger.info(f"Tailored CV saved to {output_path}")
else:
logger.error("Failed to save tailored CV")
else:
logger.error("Failed to update skills section")
except Exception as e:
logger.error(f"Error tailoring CV: {str(e)}")
sys.exit(1)
else:
logger.error("Either --job or --jobs-file must be specified")
sys.exit(1)
elif args.command == 'batch':
from job_apply_ai.cv_modifier.cv_analyzer import batch_process_jobs
output_dir = args.output_dir or os.path.join(os.getcwd(), "job_apply_ai", "outputs", "cvs")
generated_cvs = batch_process_jobs(args.jobs_file, args.cv, output_dir)
if generated_cvs:
logger.info(f"Generated {len(generated_cvs)} tailored CVs:")
for cv_path in generated_cvs:
logger.info(f" - {cv_path}")
else:
logger.warning("Failed to generate any CVs")
else:
parser.print_help()
sys.exit(1)
if __name__ == '__main__':
main()