Add a new CLI command that generates both a targeted resume and a personalized cover letter for a specific job description. The AI will be able to ask follow-up questions when needed to create a more compelling cover letter.
- AI Generator (
cli/generators/ai_generator.py): Functional, supports Claude/OpenAI - Email Template (
templates/email_md.j2): Exists, can be adapted for cover letters - Generate Command: Supports
--aiand--job-descflags for resume generation - No cover letter generation: Currently no dedicated cover letter functionality
Option A: Add --cover-letter flag to existing generate command
- Pros: Simple, unified workflow
- Cons: Couples resume and cover letter generation
Option B: Create new generate-package command (RECOMMENDED)
resume-cli generate-package --job-desc job.txt --variant v1.1.0-backend- Pros: Clean separation, makes intent explicit, allows for package-specific options
- Cons: Additional command to learn
The AI should ask follow-up questions for cover letter generation when it lacks context. Two approaches:
Interactive Mode (default):
resume-cli generate-package --job-desc job.txt
# AI asks: "What excites you about this role?"
# User answers
# AI asks: "Do you have a connection at the company?"
# User answers (or skip)
# Generation completesNon-Interactive Mode (smart guesses enabled):
resume-cli generate-package --job-desc job.txt --non-interactive
# Uses AI-generated smart guesses for optional questions
# No prompts, fully automatedDECIDED: Subdirectories per application
output/
└── {company}-{date}/
├── resume.{ext}
├── resume.pdf (if ext != pdf)
├── cover-letter.md
└── cover-letter.pdf
Cover Letter Formats: Both Markdown and PDF will be generated (user decision)
Key Requirements:
- Generate cover letter in Markdown format
- Compile to PDF (reuse existing PDF compilation from
TemplateGenerator) - Support custom template paths (future enhancement)
File: templates/cover_letter_md.j2
# {{ position_name }} Application - {{ contact.name }}
{{ contact.name }}
{{ contact.email }} | {{ contact.phone }}
{% if contact.urls.linkedin %}{{ contact.urls.linkedin }}{% endif %}
{% if contact.urls.github %}{{ contact.urls.github }}{% endif %}
{{ current_date }}
{{ hiring_manager_name|default('Hiring Manager') }}
{{ company_name }}
{{ company_address|default('') }}
{{ company_name|default('[Company Name]') }}
Dear {{ hiring_manager_name|default('Hiring Manager') }},
{{ opening_paragraph }}
{{ body_paragraphs }}
{{ closing_paragraph }}
Best regards,
{{ contact.name }}
{% if contact.urls.linkedin %}{{ contact.urls.linkedin }}{% endif %}
{% if contact.urls.github %}{{ contact.urls.github }}{% endif %}File: cli/generators/cover_letter_generator.py
class CoverLetterGenerator:
"""Generate personalized cover letters with AI."""
def __init__(self, yaml_path, config):
self.yaml_handler = ResumeYAML(yaml_path)
self.config = config
# Initialize AI client (reuse from AIGenerator)
def generate_interactive(
self,
job_description: str,
variant: str,
output_path: Optional[Path] = None,
non_interactive: bool = False
) -> tuple[str, dict]:
"""
Generate cover letter, asking questions as needed.
Returns:
(cover_letter_content, question_answers_dict)
"""
def _extract_job_details(self, job_desc: str) -> dict:
"""Extract company, position, requirements using AI."""
def _determine_questions(self, job_details: dict) -> list:
"""Decide what questions to ask based on job + resume gap."""
def _ask_question(self, question: str) -> str:
"""Prompt user and return answer."""
def _generate_with_ai(
self,
job_details: dict,
question_answers: dict,
variant: str
) -> str:
"""Generate final cover letter using AI."""The AI should ask about information not in resume.yaml:
| Question Type | Trigger Condition | Example Question |
|---|---|---|
| Company connection | Job description mentions referral | "Do you know anyone at [Company]?" |
| Motivation | Always (cover letter essential) | "What excites you about this role?" |
| Company-specific knowledge | Company has unique mission/products | "What about [Company]'s mission resonates with you?" |
| Gap addressing | Resume has employment gap | "Would you like to address your career transition?" |
| Relocation | Remote job + different location | "Are you willing to relocate?" |
| Salary expectations | Job mentions salary | "Any salary requirements?" |
When --non-interactive flag is used, the AI will generate smart guesses instead of prompting:
def _generate_smart_guesses(self, job_details: dict, resume_data: dict) -> dict:
"""Generate AI-based guesses for cover letter questions."""
# Use AI to infer:
# - Motivation: Based on job requirements + resume skills alignment
# - Company alignment: Based on company mission in job description
# - Connection: Default to "no" (safe assumption)
return {
"motivation": ai_generated_motivation,
"company_alignment": ai_generated_alignment,
"connection": None # Skip if no explicit connection
}def _determine_questions(self, job_details: dict, resume_data: dict) -> list:
questions = []
# Always ask about motivation
questions.append({
"key": "motivation",
"question": f"What specifically excites you about the {job_details['position']} role at {job_details['company']}?",
"required": True
})
# Check for company-specific context needed
if job_details.get("company_mission"):
questions.append({
"key": "company_alignment",
"question": f"What aspects of {job_details['company']}'s mission resonate with you?",
"required": False
})
# Check if user might have connections
questions.append({
"key": "connection",
"question": f"Do you have any connections at {job_details['company']}? (Press Enter to skip)",
"required": False
})
return questionsFile: cli/main.py (add new command)
@cli.command()
@click.option("-v", "--variant", default="v1.0.0-base", help="Resume variant")
@click.option("-f", "--format", type=click.Choice(["md", "tex", "pdf"]), default="md", help="Resume format")
@click.option("--job-desc", type=click.Path(exists=True), required=True, help="Job description file")
@click.option("--company", type=str, help="Company name (overrides extraction from job description)")
@click.option("--non-interactive", is_flag=True, help="Skip questions, use smart defaults")
@click.option("--no-cover-letter", is_flag=True, help="Skip cover letter generation")
@click.option("--output-dir", type=click.Path(), help="Output directory (default: config setting)")
@click.pass_context
def generate_package(ctx, variant, format, job_desc, company, non_interactive,
no_cover_letter, output_dir):
"""
Generate a complete application package: resume + cover letter.
Output is organized in subdirectories:
output/{company}-{date}/
├── resume.{ext}
├── cover-letter.md
└── cover-letter.pdf
Example:
resume-cli generate-package --job-desc job-posting.txt --variant v1.1.0-backend
resume-cli generate-package --job-desc job.txt --company "Acme Corp" --non-interactive
"""
yaml_path = ctx.obj["yaml_path"]
config = ctx.obj["config"]
job_description = Path(job_desc).read_text()
console.print("[bold blue]Generating Application Package[/bold blue]")
# 1. Generate AI-customized resume
console.print("\n[cyan]Step 1: Generating resume...[/cyan]")
from .generators.ai_generator import AIGenerator
resume_gen = AIGenerator(yaml_path, config=config)
resume_content = resume_gen.generate(
variant=variant,
job_description=job_description,
output_format=format,
output_path=resume_output_path
)
console.print(f"[green]✓[/green] Resume: {resume_output_path}")
# 2. Generate cover letter
if not no_cover_letter:
console.print("\n[cyan]Step 2: Generating cover letter...[/cyan]")
from .generators.cover_letter_generator import CoverLetterGenerator
cl_gen = CoverLetterGenerator(yaml_path, config=config)
if non_interactive:
cover_letter, qa_dict = cl_gen.generate_non_interactive(
job_description=job_description,
variant=variant,
output_path=cover_letter_output_path
)
else:
cover_letter, qa_dict = cl_gen.generate_interactive(
job_description=job_description,
variant=variant,
output_path=cover_letter_output_path
)
console.print(f"[green]✓[/green] Cover letter: {cover_letter_output_path}")
console.print("\n[bold green]Application package complete![/bold green]")File: config/default.yaml
# ... existing config ...
cover_letter:
enabled: true
template: cover_letter_md.j2
output_directory: output
formats: [md, pdf] # Generate both formats
default_questions:
- motivation
- connection
optional_questions:
- company_alignment
- relocation
- salary
# Non-interactive mode: smart guesses enabled
smart_guesses: true
# AI settings (inherit from ai: section, can override)
tone: professional # professional, enthusiastic, casual
max_length: 400 # wordsFile: cli/integrations/tracking.py
Add cover_letter_generated boolean field to tracking CSV.
test_cover_letter_generator.py: Test question determination, AI generationtest_job_detail_extraction.py: Test parsing job descriptions
- Test full
generate-packagecommand with mock job description - Test interactive mode with simulated user input
- Test non-interactive fallback behavior
-
Week 1: Core cover letter generator + template (Phase 1)
- Create
cover_letter_generator.py - Create
cover_letter_md.j2template - Implement basic AI generation (no questions yet)
- Create
-
Week 1-2: Question/Answer system (Phase 2)
- Implement
_determine_questions()logic - Implement
_ask_question()user prompts - Implement non-interactive fallback
- Implement
-
Week 2: Package command (Phase 3)
- Add
generate-packagecommand to CLI - Integrate resume + cover letter generation
- Handle output directory structure
- Add
-
Week 2: Config and tracking (Phase 4-5)
- Add cover letter config settings
- Update tracking for cover letter generation
-
Week 3: Testing and refinement
- Write tests
- Test with real job descriptions
- Refine AI prompts based on results
cli/generators/cover_letter_generator.py- Main cover letter generator with PDF supporttemplates/cover_letter_md.j2- Cover letter templatetemplates/cover_letter_tex.j2- LaTeX template for PDF compilation (optional)tests/test_cover_letter_generator.py- Tests
cli/main.py- Addgenerate-packagecommand with--companyoptionconfig/default.yaml- Add cover letter settings (formats, smart_guesses)cli/integrations/tracking.py- Track cover letter generationcli/generators/template.py- Add PDF compilation method for cover lettersCLAUDE.md- Update documentation with new command
-
Cover letter formats: Both Markdown and PDF will be generated ✅
-
Non-interactive mode: AI will make smart guesses (generic but relevant statements) ✅
-
Template customization: Users can provide custom templates (future enhancement, not v1.0 priority) ⏳
-
Company name extraction:
--companyoption added as fallback when job description doesn't clearly state company name ✅ -
Output organization: Subdirectories per application (
output/{company}-{date}/) ✅