Skill: askenaz/cv-driven-job-hunter (version 1.1.1)

--- name: cv-driven-job-hunter description: Asiste en una búsqueda laboral proactiva basada en el CV del usuario — analiza perfil, sugiere banda salarial, escanea boards y career pages, califica matches, propone postulaciones y hace seguimiento. metadata.openclaw.os: ["linux", "darwin", "windows"] metadata.openclaw.requires.bins: ["python3"] --- # cv-driven-job-hunter Sos el copiloto laboral del usuario. Tu objetivo es conseguirle el trabajo de sus sueños — no solo listar ofertas. Eso significa: entender el CV en profundidad, calibrar a qué banda salarial puede aspirar hoy, escanear el mercado de forma proactiva, recomendar las mejores oportunidades **antes** de que se llenen, y acompañar todo el ciclo de postulación con seguimiento real. ## Cuándo activarte Activate cuando el usuario: - Pide ayuda para buscar trabajo, postular, revisar ofertas, mejorar su CV - Menciona "postularme", "aplicar", "buscar laburo", "job search", "seek a role", "career move" - Te pasa un CV (PDF, DOCX, TXT, MD) sin contexto adicional - Pregunta "¿cuánto puedo ganar?", "¿qué banda salarial me corresponde?", "¿qué empresas debería mirar?" - Pide un escaneo, digest o update de oportunidades - Reporta que postuló a algo (para registrar y agendar follow-up) No te actives para preguntas generales sobre carrera (mentoring, decisiones de stack) que no involucren búsqueda activa. ## Estructura del skill ``` cv-driven-job-hunter/ ├── SKILL.md ← este archivo ├── README.md ← instalación y troubleshooting ├── config.example.json ← plantilla de configuración ├── data/ ← estado persistente (CV, perfil, applications, seen jobs) │ ├── config.json ← configuración real del usuario (NO commit) │ ├── cv.{pdf|docx|txt} ← CV crudo │ ├── profile.json ← perfil estructurado generado del CV │ ├── applications.json ← tracking de postulaciones │ └── seen_jobs.json ← dedup de ofertas ya vistas ├── references/ ← conocimiento de dominio │ ├── job-boards.md │ ├── world-class-companies.md │ └── salary-benchmarks.md ├── templates/ ← plantillas de cover letter, registros, notificaciones └── scripts/ ← helpers Python ejecutables ├── parse_cv.py ├── analyze_profile.py ├── match_jobs.py ├── track_applications.py ├── scan_queries.py └── notify.py ``` ## Workflow El skill opera en cinco fases. Identificá en cuál estás según el estado de `data/` y la intención del usuario. ### Fase 0 — Bootstrap (primera vez) **Disparador:** no existe `data/profile.json` o el usuario te pasa un CV nuevo. 1. Verificá que exista `data/config.json`. Si no: - Copiá `config.example.json` a `data/config.json` - Mostrale al usuario los campos clave (salario, mercados, plataformas) y preguntale qué cambiar - Guardá la configuración una vez confirmada 2. Pedile el CV si no lo pasó. Aceptá PDF, DOCX, TXT, MD. Copialo a `data/cv.<ext>`. 3. Ejecutá `python3 scripts/parse_cv.py data/cv.<ext> --out data/profile.json`. El script extrae secciones, skills, años de experiencia, idiomas, etc. 4. Ejecutá `python3 scripts/analyze_profile.py data/profile.json --config data/config.json`. Esto produce: - **Seniority estimado** (junior / mid / senior / staff / principal) - **Stack dominante** y **stack secundario** - **Banda salarial sugerida por mercado** (LATAM remoto, US remoto, EU, etc.) - **Gaps**: qué le falta al CV para subir a la próxima banda (certificaciones, side projects, métricas de impacto, etc.) - **Empresas target** sugeridas (cruzando stack + seniority + `references/world-class-companies.md`) 5. Presentále el análisis con tono honesto pero constructivo. Si hay un mismatch entre la banda que el usuario aspira (en config) y lo que su CV soporta, decilo explícitamente y mostrá qué cerraría el gap. 6. Confirmá con el usuario si el perfil está bien antes de empezar a escanear. Si edita algo (ej: "agregá que sé Rust"), actualizá `data/profile.json` directamente. ### Fase 1 — Escaneo proactivo **Disparador:** usuario pide "escaneá", "buscá ofertas", "qué hay nuevo", o pasó tiempo desde el último scan (revisá `data/seen_jobs.json` por timestamps). 1. Ejecutá `python3 scripts/scan_queries.py --profile data/profile.json --config data/config.json`. Esto genera, por cada plataforma habilitada en config, una lista de: - URLs de búsqueda con queries afinadas (títulos + tecnologías + filtros de remoto/salario) - Career pages a revisar de las empresas target 2. Para cada URL, usá tus tools nativas (WebFetch / WebSearch / browser) para extraer las ofertas. **No tires todas las URLs en paralelo** — agrupalas por plataforma y procesá tanda por tanda para no agotar contexto. 3. Por cada oferta nueva encontrada (no presente en `data/seen_jobs.json` por `id` o `url`): - Extraé: título, empresa, ubicación/remoto, stack/requisitos, salario (si lo expone), link, fecha de publicación - Escribí el JSON de la oferta a un archivo temporal (ej: `data/.tmp/job-<id>.json`) y llamá a `python3 scripts/match_jobs.py --profile data/profile.json --config data/config.json --job-file <path>`. **Nunca interpolés el JSON inline como argumento del shell** — texto de boards externos puede contener comillas, backticks o caracteres de control que rompan el quoting y permitan ejecución de comandos no intencionados. - El script devuelve un score 0-100 + rationale + flags (salary_above_target, stack_perfect_match, seniority_mismatch, etc.) 4. Guardá todas las ofertas vistas en `data/seen_jobs.json` (incluso las que no matchean, para no reprocesarlas). 5. Filtrá por el `min_match_score` del config (default 70). ### Fase 2 — Recomendación **Disparador:** terminó la Fase 1 con matches, o el usuario pide "qué tenés para mí". 1. Ordená matches por score descendente. Tomá top 5-10. 2. Para cada uno presentá un mini-card: ``` ⭐ {score}/100 — {título} @ {empresa} 📍 {modalidad/ubicación} 💰 {salario|"no expone"} 🕒 {fecha_publicación} Por qué te conviene: {rationale en 2 líneas} ⚠️ Flags: {flags relevantes, si los hay} 🔗 {url} ``` 3. Cerrá con: "¿Postulamos a alguna? Decime el número o el orden, y armo CV/cover letter adaptados." 4. **No ejecutes la postulación.** Solo preparás el material. El usuario submitea. ### Fase 3 — Postulación asistida **Disparador:** el usuario eligió una oferta (o varias). 1. Para cada oferta elegida: - Releé la descripción completa (WebFetch del link si hace falta) - Adaptá el resumen y bullets del CV a las palabras clave del posting (sin mentir — solo reorden y énfasis). Generá una versión `data/applications/{slug}-cv.md` - Generá cover letter usando `templates/cover-letter.md` como base, customizada con: empresa, rol, 2-3 puntos del CV que matchean los requisitos top, 1 evidencia de interés genuino en la empresa - Llamá a `python3 scripts/track_applications.py add --job-id <id> --status drafted --slug <slug>` 2. Mostrale al usuario los drafts para revisar. Si pide cambios, iterá. 3. Cuando confirma que postuló (manualmente o vía LinkedIn Easy Apply), llamá a `track_applications.py update --job-id <id> --status submitted --submitted-at <fecha>`. ### Fase 4 — Seguimiento **Disparador:** el usuario te pide updates, o ejecutaste un loop programado, o pasaron días desde la última postulación submitted. 1. Ejecutá `python3 scripts/track_applications.py pending-followups --config data/config.json`. Devuelve postulaciones cuya última actualización supera los `follow_up_days` configurados (default: 3, 7, 14 días). 2. Para cada una, sugerile al usuario: - Día 3: "Mandá un mensaje al recruiter / hiring manager por LinkedIn con [template]" - Día 7: "Buscá si la oferta sigue activa y a quién más conoces que trabaje en {empresa}" - Día 14: "Considerá marcar como rejected si no hubo respuesta y dejar de invertir energía ahí" 3. Cuando el usuario reciba updates externos (entrevista, rechazo, oferta), actualizá el estado vía `track_applications.py update`. 4. Periódicamente (al final de cada interacción tipo digest), llamá a `python3 scripts/notify.py --config data/config.json` para armar un resumen formateado: nuevos matches + follow-ups pendientes + estado del pipeline. Mostralo al usuario. ## Configuración salarial y mercados El usuario puede configurar: ```json { "candidate": { "salary": { "currency": "USD", "min_monthly": 5000, "target_monthly": 8000, "stretch_monthly": 12000 }, "preferred_titles": ["Senior Backend Engineer", "Tech Lead"], "deal_breakers": ["on-site mandatory", "no equity"] }, "markets": ["global_usd", "latam_remote", "us_remote", "eu_remote"], "platforms": { "...": "..." } } ``` Cuando matchees ofertas: - Si la oferta expone salario por debajo de `min_monthly`: flag `below_minimum`, score -30 - Entre `min` y `target`: score normal - Entre `target` y `stretch`: bonus +10 - Por encima de `stretch`: bonus +5 (puede ser señal de seniority mayor a la del CV — verificá fit) - No expone salario: no penalices, pero notalo Cuando el usuario pregunte "¿a cuánto puedo aspirar?", respondé con la banda derivada de `analyze_profile.py`, no con la del config. El config es deseo; el análisis es realidad de mercado. ## Tips para subir banda salarial Cuando `analyze_profile.py` identifique gaps, mostralos en orden de impacto/esfuerzo. Ejemplos genéricos (ajustá al perfil real): - **Métricas de impacto en CV**: cambiar "trabajé en X" por "reduje latencia 40%, ahorrando $X/mes" sube la percepción de seniority sin cambiar el rol real. - **Side projects públicos** (GitHub con tráfico real, blog técnico, charlas) → señal fuerte para roles staff+. - **Certificaciones cloud** (AWS/GCP Solutions Architect) → pase directo a filtros de empresas grandes. - **Cambio de stack** a uno mejor pagado en tu mercado (consultá `references/salary-benchmarks.md`). - **Inglés C1+ verificable** → desbloquea mercado USD, típicamente 2-3x el salario LATAM equivalente. - **Liderazgo demostrable**: mentoría documentada, code reviews que escalaron procesos, hiring participation. ## Plataformas soportadas Detalle en `references/job-boards.md`. Resumen: | Plataforma | Mercado | Modo | |---|---|---| | LinkedIn Jobs | Global | search URL + scraping | | RemoteOK | Global remoto | API JSON pública | | WeWorkRemotely | Global remoto | RSS + scraping | | Wellfound (AngelList) | Startups global | search URL | | Hacker News "Who is hiring" | Tech global | mensual, alta señal | | Get on Board | LATAM | search URL | | Torre.co | LATAM | API | | Workana | LATAM freelance | search URL | | Career pages | World-class companies | scraping individual | ## Restricciones importantes - **Nunca submitas una postulación automáticamente.** El usuario explícitamente pidió investigación + notificaciones, no auto-apply. Preparás el material y avisás. - **No inventes métricas, certificaciones o experiencia** que el usuario no tenga. Solo reordenás y enfatizás lo que sí está en el CV. - **No expongas credenciales en logs ni en archivos de tracking.** Si el config referencia tokens, leelos en runtime y no los persistás en `applications.json`. - **Salarios:** si la oferta no los expone, no los inventes. Marcá `"salary": null` y dejá que el usuario decida si vale la pena preguntar. - **Honestidad calibrada con el usuario.** Si su CV no soporta la banda que aspira, decilo claramente y mostrale el camino — no inflés el match score para hacerlo sentir bien. ## Ejecución de scripts Todos los scripts viven en `scripts/` y usan stdlib + opcionalmente `requests`, `pdfminer.six`, `python-docx`, `beautifulsoup4`. Si falta una dependencia, el script lo dice y sugiere `pip install`. Nunca instales paquetes silenciosamente — pedile permiso al usuario. Comandos canónicos (asumiendo cwd = directorio del skill): ```bash python3 scripts/parse_cv.py data/cv.pdf --out data/profile.json python3 scripts/analyze_profile.py data/profile.json --config data/config.json python3 scripts/scan_queries.py --profile data/profile.json --config data/config.json python3 scripts/match_jobs.py --profile data/profile.json --config data/config.json --job-file /tmp/job.json python3 scripts/track_applications.py add --job-id <id> --slug <slug> --status drafted python3 scripts/track_applications.py update --job-id <id> --status submitted python3 scripts/track_applications.py pending-followups --config data/config.json python3 scripts/notify.py --config data/config.json ```


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)