apply
무료Searches for jobs on LinkedIn, filters by profile Must-haves, applies via Easy Apply, registers each application in DB.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Optimizes the user's LinkedIn profile and CV to align with their declared professional goals. Audits, redacts improvements, applies with per-section approval, and exports a polished CV to PDF.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
실행 점수 미검증
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
Keyword: polish (or variants: "mejorar mi linkedin", "pulir perfil", "alinear cv", "optimizar perfil")
Takes the user's captured profile (users.data.profile) and job preferences (users.data.job_preferences) and uses them to optimize the two artifacts that recruiters see: the LinkedIn profile and the CV. This is an output flow, not an input flow — profile captures data, polish applies it externally.
onboarding (DB, browser profile, LinkedIn session)profile (requires users.data.profile and users.data.job_preferences with Must/Strong/Nice weights)polish can run alongside other flows (e.g: apply, news, targets) by using an attached session:
node .agents/skills/browser-automation/scripts/browser.js open <url>
node .agents/skills/browser-automation/scripts/browser.js exec eval '<code>' --session polish-1
node scripts/generate-cv.js --session polish-1
node .agents/skills/browser-automation/scripts/browser.js exec close --session polish-1
All browser commands and generate-cv.js accept --session. When done, close only your own session (exec close --session polish-1) or leave it — never close-all, which kills the browser for other agents. See AGENTS.md "Parallel execution".
Before executing any phase, verify that dependencies are satisfied. If any check fails, do not proceed — tell the user what is missing and how to resolve it:
# 1. Verify onboarding completed: DB exists and has user
node scripts/db.js "SELECT id, name, email, data FROM users WHERE id = <user_id>"
# If no row → "Necesitas ejecutar `onboarding` primero. No hay DB configurada."
# 2. Verify profile exists with minimum data
node scripts/db.js "SELECT data->'profile' AS profile, data->'job_preferences' AS prefs FROM users WHERE id = <user_id>"
# If profile is null/empty → "Necesitas ejecutar `profile` primero. No hay perfil capturado."
# If job_preferences is null/empty → "Necesitas completar el cuestionario de `profile`. No hay preferencias declaradas."
# 3. Verify minimum fields within profile
# Required for Phase 1 (LinkedIn): profile.title, profile.experience[], profile.skills[]
# Required for Phase 2 (CV): profile.full_name, profile.email, profile.experience[], profile.education[]
# If any required field missing → "Tu perfil esta incompleto. Falta: <fields>. Ejecuta `profile` para completarlo."
# 4. Verify LinkedIn session is active
node .agents/skills/browser-automation/scripts/browser.js status # lists active sessions
# If fails → "Necesitas iniciar sesion en LinkedIn. Ejecuta `onboarding` o abre el browser headed para login."
# 5. Verify linkedin_profile URL exists in DB
node scripts/db.js "SELECT data->'linkedin_profile' AS url FROM users WHERE id = <user_id>"
# If null → "No tengo tu URL de LinkedIn. Ejecuta `onboarding` para guardarla."
Only if all 5 checks pass, continue to Phase 1.
profile, job_preferences, linkedin_profile, style_profile, strategy (for strategy_level)node .agents/skills/browser-automation/scripts/browser.js goto <linkedin_profile_url>node .agents/skills/browser-automation/scripts/browser.js exec snapshoteval (adapt selectors to what you see in the snapshot):
node .agents/skills/browser-automation/scripts/browser.js exec eval '(function(){
// Adapt selectors based on current LinkedIn DOM.
// LinkedIn changes their UI frequently, so read the snapshot first
// and adjust these selectors as needed.
var headline = document.querySelector("h1")?.textContent?.trim() || "";
var about = document.querySelector("#about ~ * .display-text, #about + * .inline-show-more-text")?.textContent?.trim() || "";
// Experience: iterate over section entries
var expNodes = document.querySelectorAll("#experience ~ * .pvs-entity, [data-view-name*='experience'] .pvs-entity");
var experience = Array.from(expNodes).map(function(n) {
return {
title: n.querySelector(".t-14 .t-bold span")?.textContent?.trim() || "",
company: n.querySelector(".t-14:not(.t-bold) span")?.textContent?.trim() || "",
description: n.querySelector(".t-14.t-normal.t-black--light span")?.textContent?.trim() || ""
};
});
// Skills
var skillNodes = document.querySelectorAll("#skills ~ * .pvs-entity, [data-view-name*='skill'] .pvs-entity");
var skills = Array.from(skillNodes).map(function(n) {
return n.querySelector(".t-14 .t-bold span")?.textContent?.trim() || "";
}).filter(Boolean);
return JSON.stringify({ headline: headline, about: about, experience: experience, skills: skills });
})()'
users.data.linkedin_snapshotjob_preferences.role_types and ai_focus?job_preferences.stack and AI-related skills?active/aggressive)?For each section with gaps, draft all changes for that section and show them together to the user for approval:
profile.title + top skills + job_preferences.ai_focus. Example: "<Title> | <Top Skills> | <Work Mode>"profile.experience[])job_preferences.stackstrategy_level is active or aggressive, activate "Open to work" with roles from job_preferences.role_types and job_preferences.seniorityPer-section approval flow:
eval (see below)users.data.linkedin_polish_log (audit trail with before/after)LinkedIn uses direct URLs to edit each section:
https://www.linkedin.com/in/<vanity>/edit/details/ → click pencil icon on headlinehttps://www.linkedin.com/in/<vanity>/edit/details/ → click pencil icon on abouthttps://www.linkedin.com/in/<vanity>/edit/details/experiences/https://www.linkedin.com/in/<vanity>/edit/details/skills/https://www.linkedin.com/in/<vanity>/edit/details/recruiteroptin/LinkedIn editors are contenteditable (tiptap/slate). The agent interacts with them via node .agents/skills/browser-automation/scripts/browser.js exec eval '<code>'. Always take a snapshot first to find the correct refs/selectors, then:
node .agents/skills/browser-automation/scripts/browser.js exec eval 'document.querySelector("button[aria-label*=\"Edit\"]").click()'
# For text inputs (headline):
node .agents/skills/browser-automation/scripts/browser.js exec eval '(function(){
var input = document.querySelector("input[type=\"text\"]");
input.value = "<new headline text>";
input.dispatchEvent(new Event("input", {bubbles: true}));
input.dispatchEvent(new Event("change", {bubbles: true}));
})()'
# For contenteditable (about, experience descriptions):
node .agents/skills/browser-automation/scripts/browser.js exec eval '(function(){
var editor = document.querySelector("[contenteditable=\"true\"]");
editor.focus();
editor.textContent = "<new text>";
editor.dispatchEvent(new InputEvent("input", {bubbles: true, inputType: "insertText"}));
editor.dispatchEvent(new Event("change", {bubbles: true}));
})()'
node .agents/skills/browser-automation/scripts/browser.js exec eval 'document.querySelector("button[type=\"submit\"], button[aria-label*=\"Save\"]").click()'
These are starting points. Always take a snapshot after navigating to the edit page and adapt selectors to what you see. LinkedIn's DOM changes frequently. The agent's advantage over a hardcoded script is that it can adapt to the current DOM in real time.
profile.cv_path (PDF) or profile.cv_urljob_preferencesusers.data.cv_markdown (Markdown content, for future iterations)The PDF flow uses scripts/generate-cv.js:
node .agents/skills/browser-automation/scripts/browser.js open file://<path> --headlessexec pdf <path>)users.data.cv_path (updates existing path)The user never sees Markdown or HTML. They see only the final PDF. If they want adjustments, they tell the agent what to change and the agent regenerates.
The optimized base CV is generic to the target role. For specific applications, the apply or targets flow can do "light tailoring" of the base CV (reorder skills, adjust summary to mention the company). This is documented as a future extension, not implemented now.
New JSONB keys in users.data:
| Key | Type | What it holds | Written by | Read by |
|---|---|---|---|---|
linkedin_snapshot | object | Current LinkedIn profile state at last audit: headline, about, experience[], skills[], education[], open_to_work | polish | polish (compare before/after), news (context) |
linkedin_polish_log | array | Audit trail of applied changes: [{section, before, after, applied_at}] | polish | polish (re-audit) |
cv_markdown | string | Optimized CV in Markdown format | polish | apply, targets (tailoring), polish (iteration) |
Existing keys that get updated:
| Key | Note |
|---|---|
cv_path | Updated to the path of the new generated PDF |
## Polish report
### LinkedIn profile
| Section | Status | Changes applied |
|---|---|---|
| Headline | Updated | "<old>" → "<new>" |
| About | Updated | Added AI focus paragraph + CTA |
| Experience (3 roles) | Updated | Rewrote 8 bullets as quantified achievements |
| Skills | Reordered | Pinned: <skill1>, <skill2>, <skill3> |
| Open to work | Activated | Roles: <role1>, <role2>, <role3> |
### CV
- Format: Markdown → PDF (via headless browser)
- Sections optimized: summary, experience, skills
- Saved to: users.data.cv_markdown + <pdf_path>
### Pending (need attention)
- [manual] LinkedIn "Featured" section: add 2-3 projects (requires manual curation)
[TODO: add metric] for the user to complete<placeholder> syntaxbefore in linkedin_polish_log. The user can revertscripts/generate-cv.jsConverts cv_markdown from DB (or --markdown <path>) to a PDF via browser headless.
node scripts/generate-cv.js [--output <path>] [--markdown <path>] [--session <name>]
If --markdown is not provided, reads users.data.cv_markdown from DB. If --output is not provided, saves to .browser-profile/cv-polished-<timestamp>.pdf. Updates users.data.cv_path in DB after generating.
This is the only script in the polish flow. LinkedIn profile audit and editing are done directly by the agent via node .agents/skills/browser-automation/scripts/browser.js exec eval and node .agents/skills/browser-automation/scripts/browser.js exec snapshot, which allows the agent to adapt to LinkedIn's DOM in real time rather than relying on hardcoded selectors.
onboarding (DB, browser profile, LinkedIn session)profile (requires users.data.profile and users.data.job_preferences)apply and targets can consume cv_markdown and cv_path for future tailoring아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
Searches for jobs on LinkedIn, filters by profile Must-haves, applies via Easy Apply, registers each application in DB.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Control a dedicated browser via agent-browser (Vercel). Use when automating web apps, scraping authenticated sites, filling forms, or navigating SPAs. Do NOT use for desktop apps or API-only integrations.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Periodic routine that runs news, cleans up inbox and applies if there's no recent activity. Designed to run 1-2 times per day.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Opens a local web dashboard that visualizes the job application pipeline, funnel stats, recent messages, and target company status. The agent opens it at the end of a round so the user can visually review the pipeline.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Safe Postgres CLI. Reads DATABASE_URL from .env, runs SQL, prints JSON. Read-only by default.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Autonomous user preference detection, storage and injection. The agent detects preferences from conversation, saves them to Postgres, and loads active ones at the start of every flow.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.