-
Notifications
You must be signed in to change notification settings - Fork 113
feat: Add job-apply-agent AgentKit #76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # Lamatic API Configuration | ||
| # Get these from your Lamatic dashboard → Project Settings | ||
| LAMATIC_API_KEY=your_api_key_here | ||
| LAMATIC_PROJECT_ID=your_project_id_here | ||
| LAMATIC_FLOW_ID=your_flow_id_here | ||
| LAMATIC_API_URL=https://your-project.lamatic.dev/graphql |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # Dependencies | ||
| node_modules/ | ||
| .pnp | ||
| .pnp.js | ||
|
|
||
| # Environment variables — NEVER commit these | ||
| .env | ||
| .env.local | ||
| .env.development.local | ||
| .env.test.local | ||
| .env.production.local | ||
|
|
||
| # Next.js | ||
| .next/ | ||
| out/ | ||
|
|
||
| # Build output | ||
| dist/ | ||
| build/ | ||
|
|
||
| # Misc | ||
| .DS_Store | ||
| *.pem | ||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| # ApplyBud — Job Apply Agent | ||
|
|
||
| An AI agent that evaluates job postings against a candidate's resume, scores the match, and auto-generates tailored cover letters for qualified roles. | ||
|
|
||
| ## What it does | ||
|
|
||
| 1. Accepts a candidate resume (plain text) and a list of job posting URLs | ||
| 2. Fetches and cleans each job description | ||
| 3. Extracts structured requirements using AI | ||
| 4. Scores the candidate's skills against each role (0–100) | ||
| 5. Generates a professional, tailored cover letter for any job scoring ≥ 70 | ||
| 6. Returns results ranked by match score with qualified jobs first | ||
|
|
||
| ## Setup | ||
|
|
||
| ### 1. Clone and install | ||
|
|
||
| ```bash | ||
| git clone <repo-url> | ||
| cd kits/agentic/job-apply-agent | ||
| npm install | ||
| ``` | ||
|
|
||
| ### 2. Configure environment variables | ||
|
|
||
| ```bash | ||
| cp .env.example .env.local | ||
| ``` | ||
|
|
||
| Fill in your values: | ||
| - `LAMATIC_API_KEY` — from Lamatic dashboard → Project Settings → API Keys | ||
| - `LAMATIC_PROJECT_ID` — your Lamatic project ID | ||
| - `LAMATIC_FLOW_ID` — the ID of your deployed job-apply-flow | ||
|
|
||
| ### 3. Import the flow | ||
|
|
||
| In your Lamatic dashboard: | ||
| 1. Go to Flows → Import | ||
| 2. Upload the flow config from `flows/job-apply-flow/config.json` | ||
| 3. Set your Groq API credential named `ApplyBud` | ||
| 4. Deploy the flow | ||
| 5. Copy the Flow ID into your `.env.local` | ||
|
|
||
| ### 4. Run locally | ||
|
|
||
| ```bash | ||
| npm run dev | ||
| ``` | ||
|
|
||
| Open [http://localhost:3000](http://localhost:3000) | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Via UI | ||
| 1. Paste your resume text into the Resume field | ||
| 2. Add one or more direct job posting URLs | ||
| 3. Click Analyse | ||
| 4. View match scores and generated cover letters | ||
|
|
||
| ### Via API | ||
|
|
||
| ```bash | ||
| curl -X POST https://your-lamatic-endpoint/graphql \ | ||
| -H "Content-Type: application/json" \ | ||
| -H "Authorization: Bearer YOUR_API_KEY" \ | ||
| -d '{ | ||
| "resume": "Your resume text here...", | ||
| "job_urls": [ | ||
| "https://jobs.example.com/senior-engineer-123" | ||
| ] | ||
| }' | ||
| ``` | ||
|
|
||
| ## Flow Architecture | ||
|
|
||
| ``` | ||
| API Request (trigger) | ||
| ↓ | ||
| Resume Parser (Generate JSON) — extracts candidate profile | ||
| ↓ | ||
| Fetch Job Pages (Code) — fetches + strips HTML from URLs | ||
| ↓ | ||
| Loop over job pages | ||
| ↓ [per job] | ||
| Job Analyser (Generate JSON) — extracts JD requirements | ||
| ↓ | ||
| Match Scorer (Code) — scores candidate vs job (0-100) | ||
| ↓ | ||
| Condition: score >= 70? | ||
| ↓ yes | ||
| Cover Letter Generator (Generate Text) | ||
| ↓ [outside loop] | ||
| Bundle Results (Code) — merges + sorts all results | ||
| ↓ | ||
| API Response | ||
| ``` | ||
|
|
||
| ## Model | ||
|
|
||
| All AI nodes use `groq/llama-3.1-8b-instant` via Groq. | ||
|
|
||
| ## Score Threshold | ||
|
|
||
| Default match threshold is **70/100**. Jobs scoring below 70 are returned without a cover letter. To change the threshold, update the Condition node in the flow. | ||
|
|
||
| ## Input Requirements | ||
|
|
||
| - **Resume**: Plain text only. No PDF, no markdown. Copy-paste from your resume. | ||
| - **Job URLs**: Must be direct job posting pages (not search results or listing pages). Each URL should open a single specific role. | ||
|
|
||
| ## Tech Stack | ||
|
|
||
| - Next.js 14 (App Router) | ||
| - TypeScript | ||
| - Lamatic SDK | ||
| - Groq (llama-3.1-8b-instant) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| "use server"; | ||
|
|
||
| import { runApplyBudFlow, ApplyBudResponse } from "@/lib/lamatic-client"; | ||
|
|
||
| export interface OrchestrateInput { | ||
| resume: string; | ||
| job_urls: string[]; | ||
| } | ||
|
|
||
| export interface OrchestrateResult { | ||
| success: boolean; | ||
| data?: ApplyBudResponse; | ||
| error?: string; | ||
| } | ||
|
|
||
| export async function orchestrate( | ||
| input: OrchestrateInput | ||
| ): Promise<OrchestrateResult> { | ||
| if (!input.resume || input.resume.trim().length < 50) { | ||
| return { | ||
| success: false, | ||
| error: "Resume text is too short. Please paste your full resume.", | ||
| }; | ||
| } | ||
|
|
||
| if (!input.job_urls || input.job_urls.length === 0) { | ||
| return { | ||
| success: false, | ||
| error: "Please provide at least one job posting URL.", | ||
| }; | ||
| } | ||
|
|
||
| const validUrls = input.job_urls.filter((url) => { | ||
| try { | ||
| new URL(url); | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| }); | ||
|
|
||
| if (validUrls.length === 0) { | ||
| return { | ||
| success: false, | ||
| error: "No valid URLs found. Please check your job posting URLs.", | ||
| }; | ||
| } | ||
|
|
||
| try { | ||
| const result = await runApplyBudFlow({ | ||
| resume: input.resume.trim(), | ||
| job_urls: validUrls, | ||
| }); | ||
|
|
||
| return { | ||
| success: true, | ||
| data: result, | ||
| }; | ||
| } catch (error) { | ||
| return { | ||
| success: false, | ||
| error: | ||
| error instanceof Error | ||
| ? error.message | ||
| : "An unexpected error occurred. Please try again.", | ||
| }; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,16 @@ | ||||||||||||||||||
| export const metadata = { | ||||||||||||||||||
| title: 'Next.js', | ||||||||||||||||||
| description: 'Generated by Next.js', | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+1
to
+4
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace template metadata with product metadata. At Line 2 and Line 3, the app still uses boilerplate values ( Suggested metadata update export const metadata = {
- title: 'Next.js',
- description: 'Generated by Next.js',
+ title: 'ApplyBud — Job Apply Agent',
+ description: 'Analyze job links against a resume, rank matches, and auto-generate tailored cover letters for high-fit roles.',
}📝 Committable suggestion
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| export default function RootLayout({ | ||||||||||||||||||
| children, | ||||||||||||||||||
| }: { | ||||||||||||||||||
| children: React.ReactNode | ||||||||||||||||||
| }) { | ||||||||||||||||||
| return ( | ||||||||||||||||||
| <html lang="en"> | ||||||||||||||||||
| <body>{children}</body> | ||||||||||||||||||
| </html> | ||||||||||||||||||
| ) | ||||||||||||||||||
| } | ||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.