138 lines
3.8 KiB
Python
138 lines
3.8 KiB
Python
"""
|
|
Generate a tailored résumé summary using AI (OpenRouter API).
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import requests
|
|
import pyperclip
|
|
from dotenv import load_dotenv
|
|
|
|
|
|
def load_profile(path: str = "./base.json") -> dict:
|
|
"""Load the user's profile from a JSON file."""
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
|
|
|
|
def load_job_description(from_clipboard: bool = True, path: str = None) -> str:
|
|
"""
|
|
Load the job description from clipboard or a file.
|
|
|
|
Args:
|
|
from_clipboard: If True, read from system clipboard
|
|
path: If from_clipboard is False, read from this file path
|
|
|
|
Returns:
|
|
The job description text
|
|
"""
|
|
if from_clipboard:
|
|
return pyperclip.paste().strip()
|
|
if path:
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
return f.read().strip()
|
|
raise ValueError("No job description source provided.")
|
|
|
|
|
|
def _build_prompt(profile: dict, jd: str) -> str:
|
|
"""Build the prompt for the AI model."""
|
|
return f"""
|
|
You are generating a tailored résumé summary for me.
|
|
|
|
Requirements:
|
|
- Use keywords found in the job description.
|
|
- Keep it concise but meaningful. Avoid fluff. Avoid long-winded text.
|
|
- Include just enough detail to feel real and grounded.
|
|
- Gently convey that I care about helping people and doing good work.
|
|
- Do NOT invent experience or skills I don't have.
|
|
- Maintain a warm, confident, human tone.
|
|
- Target THIS specific job directly, so use ATS keywords, while remaining natural.
|
|
- Use the profile to add context and details.
|
|
|
|
My profile (JSON fields merged):
|
|
{json.dumps(profile, indent=2)}
|
|
|
|
Job description:
|
|
{jd}
|
|
|
|
Write the résumé summary now.
|
|
"""
|
|
|
|
|
|
def _call_openrouter(prompt: str, model: str, api_key: str) -> str:
|
|
"""Call OpenRouter API to generate text."""
|
|
url = "https://openrouter.ai/api/v1/chat/completions"
|
|
|
|
headers = {
|
|
"Authorization": f"Bearer {api_key}",
|
|
"HTTP-Referer": "http://localhost",
|
|
"X-Title": "ResumeSummaryScript",
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
payload = {
|
|
"model": model,
|
|
"messages": [{"role": "user", "content": prompt}],
|
|
}
|
|
|
|
response = requests.post(url, headers=headers, json=payload)
|
|
|
|
if response.status_code != 200:
|
|
raise RuntimeError(f"OpenRouter error {response.status_code}: {response.text}")
|
|
|
|
data = response.json()
|
|
return data["choices"][0]["message"]["content"]
|
|
|
|
|
|
def generate_resume_summary(
|
|
profile_path: str = "./base.json",
|
|
job_description: str = None,
|
|
from_clipboard: bool = True,
|
|
copy_to_clipboard: bool = True,
|
|
) -> str:
|
|
"""
|
|
Generate a tailored résumé summary using AI.
|
|
|
|
Uses the user's profile and a job description to generate a personalized
|
|
summary section for a résumé, targeting the specific job.
|
|
|
|
Args:
|
|
profile_path: Path to the profile JSON file
|
|
job_description: Job description text (if None, uses from_clipboard/path)
|
|
from_clipboard: If job_description is None, read JD from clipboard
|
|
copy_to_clipboard: If True, copy the generated summary to clipboard
|
|
|
|
Returns:
|
|
The generated résumé summary text
|
|
"""
|
|
load_dotenv()
|
|
|
|
api_key = os.getenv("OPENROUTER_API_KEY")
|
|
model = os.getenv("MODEL", "openai/gpt-4o-mini")
|
|
|
|
if not api_key:
|
|
raise RuntimeError("Missing OPENROUTER_API_KEY in .env")
|
|
|
|
profile = load_profile(profile_path)
|
|
|
|
if job_description is None:
|
|
jd = load_job_description(from_clipboard=from_clipboard)
|
|
else:
|
|
jd = job_description
|
|
|
|
prompt = _build_prompt(profile, jd)
|
|
summary = _call_openrouter(prompt, model, api_key)
|
|
|
|
if copy_to_clipboard:
|
|
pyperclip.copy(summary)
|
|
|
|
return summary
|
|
|
|
|
|
if __name__ == "__main__":
|
|
summary = generate_resume_summary()
|
|
|
|
print("\n=== Generated Summary ===\n")
|
|
print(summary)
|
|
print("\n[Summary copied to clipboard]\n")
|