File size: 7,898 Bytes
56c7b6d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
"""
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()