feat: lockfile to prevent parallel runs + AI keywords lib
This commit is contained in:
61
lib/keywords.mjs
Normal file
61
lib/keywords.mjs
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* keywords.mjs — AI-generated search keywords
|
||||
* One Claude call per search track using full profile + search config context
|
||||
*/
|
||||
|
||||
export async function generateKeywords(search, profile, apiKey) {
|
||||
if (!apiKey) throw new Error('ANTHROPIC_API_KEY not set');
|
||||
|
||||
const prompt = `You are an expert job search strategist helping a candidate find the right roles on LinkedIn and Wellfound.
|
||||
|
||||
## Candidate Profile
|
||||
- Name: ${profile.name.first} ${profile.name.last}
|
||||
- Location: ${profile.location.city}, ${profile.location.state} (remote only)
|
||||
- Years experience: ${profile.years_experience}
|
||||
- Desired salary: $${profile.desired_salary.toLocaleString()}
|
||||
- Work authorization: Authorized to work in US + UK, no sponsorship needed
|
||||
- Willing to relocate: ${profile.willing_to_relocate ? 'Yes' : 'No'}
|
||||
- Background summary: ${profile.cover_letter?.substring(0, 400)}
|
||||
|
||||
## Job Search Track: "${search.name}"
|
||||
- Salary minimum: $${(search.salary_min || 0).toLocaleString()}
|
||||
- Platforms: ${(search.platforms || []).join(', ')}
|
||||
- Remote only: ${search.filters?.remote ? 'Yes' : 'No'}
|
||||
- Exclude these keywords: ${(search.exclude_keywords || []).join(', ')}
|
||||
- Current keywords already in use: ${(search.keywords || []).join(', ')}
|
||||
|
||||
## Task
|
||||
Generate 15 additional LinkedIn/Wellfound job search query strings to find "${search.name}" roles for this candidate.
|
||||
|
||||
Think about:
|
||||
- How do startups and hiring managers actually title these roles at seed/Series A/B companies?
|
||||
- What variations exist across industries (fintech, devtools, data infra, security, AI/ML)?
|
||||
- What seniority + function combinations surface the best matches?
|
||||
- What terms does this specific candidate's background match well?
|
||||
- Do NOT repeat keywords already listed above
|
||||
- Do NOT use excluded keywords
|
||||
|
||||
Return ONLY a JSON array of strings, no explanation, no markdown.
|
||||
Example format: ["query one", "query two", "query three"]`;
|
||||
|
||||
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': apiKey,
|
||||
'anthropic-version': '2023-06-01'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'claude-3-haiku-20240307',
|
||||
max_tokens: 1024,
|
||||
messages: [{ role: 'user', content: prompt }]
|
||||
})
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (data.error) throw new Error(data.error.message);
|
||||
|
||||
const text = data.content[0].text.trim();
|
||||
const clean = text.replace(/```json\n?|\n?```/g, '').trim();
|
||||
return JSON.parse(clean);
|
||||
}
|
||||
Reference in New Issue
Block a user