Tu vigía de ofertas dev remoto. AI-powered job aggregator CLI.
Atalaya scrapes dev job boards, scores offers against your profile, generates tailored cover letters with Claude, and applies on your behalf (email + Playwright forms). Built for remote devs in Spain/EU hunting their next role.
atalaya-cli is a Python CLI that:
- Aggregates remote dev offers from the Spanish job boards the international tools skip, plus the remote-first aggregators.
- Deduplicates and stores them locally (SQLite).
- Scores each offer against your profile (stack, seniority, location).
- Generates tailored cover letters and CV variants with the Claude API.
- Exports shortlists to CSV/JSON for manual review.
- Applies automatically to offers exposing a contact email (SMTP). Rate-limited (1 application / 5 minutes by default) to avoid spam detection.
Not on PyPI yet, so install from source:
git clone https://github.com/ElRaxy/atalaya-cli && cd atalaya-cli
python3 -m venv .venv && source .venv/bin/activate
pip install -e .Then install the agent skill into your AI coding CLI (Claude Code, Codex, OpenCode):
bhound skill installbhound init
bhound search --board all --remote-only
bhound list --min-score 60
bhound letter <offer-id>
bhound cv <offer-id>
bhound apply <offer-id> --preview # dry-run, check email target
bhound apply <offer-id> # send email (offers with contact email)
bhound apply-manual <offer-id> # for form-only offers: copy letter, open browser
bhound apply-manual <offer-id> --mark-applied # re-run to record APPLIED in DB
bhound apply-batch --min-score 70 --limit 5| Board | Source | Notes |
|---|---|---|
remoteworkspain |
remoteworkspain.es | JSON-LD JobPosting on detail pages. |
jobfluent |
jobfluent.com | Server-side HTML. Barcelona/EU startup jobs. |
himalayas |
himalayas.app (country=Spain) | Remote-first. Next.js SSR HTML. |
indeed_es |
es.indeed.com | Blocked by Cloudflare on direct HTTP. Parser ready for Playwright. |
remoteok |
remoteok.com (JSON API) | Worldwide / Europe / EMEA. Dev-only filter. |
weworkremotely |
weworkremotely.com (RSS) | 4 programming categories. Excludes USA-only. |
tecnoempleo |
tecnoempleo.com | Spanish dev jobs, remote filter. |
infojobs |
infojobs.net | Spain generalist. First 5 cards per listing (JS scroll for the rest). |
linkedin_public |
linkedin.com/jobs-guest | No auth. Spain + remote (f_WT=2). ~25 offers/run. |
Run one board with --board <name> or all in parallel with --board all.
A typical run yields several hundred offers before scoring + deduplication. Of the 9
registered readers, 8 answer — measured across five spaced runs on 2026-08-23. The one that
does not is himalayas, and not because of the captcha: Scrapling solves the Turnstile and the
origin still returns 403, so the block comes from the WAF. indeed_es does return offers again
with the stealth extra (16 cards on all five runs).
indeed_es returns 0 offers until the Playwright fallback ships.
Atalaya uses the Anthropic API (Claude) to generate tailored cover letters and CV variants per offer. Prompt caching is enabled on the system prompt to reduce cost across multiple calls in the same session.
bhound letter 3 --lang es
bhound letter 3 --lang en --tone warm --out applications/zendrop-letter.md
bhound cv 3 --lang en --out applications/zendrop-cv.md
bhound export --fmt csv --out shortlistAtalaya supports two ways to talk to Claude. The default is CLI — it spawns
claude -p as a subprocess and rides on your existing Claude Code subscription
(Pro / Max / Team) via the OAuth keychain. No ANTHROPIC_API_KEY required, no
extra billing.
[claude]
backend = "cli" # default — uses Claude Code subscription
model = "claude-sonnet-4-6" # also supports claude-haiku-4-5-20251001, claude-opus-4-7Override per-call with the env var ATALAYA_CLAUDE_BACKEND=api|cli.
To use the API backend (your own ANTHROPIC_API_KEY, separate billing):
pip install -e ".[api]"Then in config.toml:
[claude]
backend = "api"
[anthropic]
api_key = "sk-ant-..."(ANTHROPIC_API_KEY env var is also picked up.)
Some job boards sit behind a Cloudflare challenge and answer 403 to a plain
HTTP GET. The stealth extra adds Scrapling,
which drives a real browser and solves the challenge:
pip install -e ".[stealth]"
scrapling install # downloads the browser binaries (~1 GB cache)No configuration needed — scrapers that benefit from it pick it up automatically and fall back to plain HTTP when the extra is absent.
| source | plain HTTP | with stealth |
|---|---|---|
indeed_es |
403 |
200 — works (16 cards/page, measured 2026-08-23) |
himalayas |
403 |
403 — Turnstile "managed" + WAF, the bypass does not get through |
The extra is not a dependency of the base package: it pulls browser binaries and most sources do not need it.
The cv command reads the base CV from projects/job-search/cv/cv-{lang}.md by default
(relative to the directory you run bhound from). Override with the env var
ATALAYA_BASE_CV_DIR (absolute path to the directory containing cv-es.md / cv-en.md)
or the per-call flag --cv-base PATH. The file format is plain Markdown — Atalaya passes
it verbatim to the Claude API as the base CV to tailor against each offer.
Atalaya can send your cover letter + CV variant by email to offers that expose a contact address.
The expected flow is letter → cv → apply — apply recovers the persisted letter and
CV variant from the local SQLite and reuses them. If neither has been generated for the offer,
apply emits a warning and falls back to a short generic body without attachment.
bhound letter 42 # generate tailored cover letter (saved to DB)
bhound cv 42 # generate tailored CV variant (saved to DB, preserves letter)
bhound apply 42 --preview # show what would happen, no email sent
bhound apply 42 # actually send (5-min rate-limit enforced)
bhound apply-batch --min-score 70 --limit 5SMTP config in <config_dir>/config.toml:
[smtp]
host = "smtp.gmail.com"
port = 587
user = "you@example.com"
password = "<gmail-app-password>" # NEVER your real password — use App Passwords
from_name = "Alex Mico"
from_email = "you@example.com"
starttls = trueRate-limit: persistent across CLI invocations, 5 min between applies + random jitter (±90s).
Override with --force (carries detection risk).
LinkedIn, InfoJobs, Tecnoempleo and most company portals require their own form (no email exposed). Atalaya helps you apply to those in ~30s per offer without any automation against the platform (zero ban risk):
bhound apply-manual 42 # prepares dossier, opens browser
# (paste letter in the form, attach CV, click Submit yourself)
bhound apply-manual 42 --mark-applied # re-run to record APPLIED in DBWhat it does:
- Recovers the persisted letter + CV variant from SQLite.
- Writes both to
<tmp>/atalaya-apply-<id>/{letter.md,cv.md}(paths printed). - Copies the cover letter to your system clipboard (Windows
clip/ macOSpbcopy/ Linuxxclip/xsel/wl-copy). - Opens the offer URL in your default browser.
- You paste, attach the CV file, click Submit in the browser.
- Re-run with
--mark-appliedto mark the offer as APPLIED in the DB.
Automated LinkedIn Easy Apply / InfoJobs Playwright are intentionally not implemented (ADR-0047, 2026-05-16). Selenium/Playwright against anti-bot detection is brittle and carries real ban risk on a personal account.
Atalaya is intentionally email-apply-only. If you need LinkedIn Easy Apply automation, combine Atalaya (scoring + tailored letters + CV variants) with one of these external tools:
- Manual outreach to recruiters — empirically more effective than mass Easy Apply.
- Simplify.jobs (free Chrome extension) — autofills forms per-offer.
- LazyApply (paid SaaS, ~$129) — mass Easy Apply for LinkedIn.
These run independently; Atalaya does not depend on or integrate with any of them.
Atalaya also reads job alert emails from your inbox (LinkedIn / InfoJobs / Tecnoempleo / RemoteOK)
via IMAP and pushes the offers into SQLite. Idempotent: each Message-ID is recorded in
email_seen, so re-running won't double-import.
bhound ingest-email --folder INBOX --since-days 7IMAP config in <config_dir>/config.toml:
[imap]
host = "imap.gmail.com"
port = 993
user = "you@example.com"
password = "<gmail-app-password>" # same App Password used for SMTP works here
use_ssl = trueThe 4 supported parsers (email_linkedin, email_infojobs, email_tecnoempleo,
email_remoteok) auto-dispatch by From header. Unknown senders are skipped.
Alpha. 9 scrapers operational, AI generators working, email apply working, email IMAP ingest working, Playwright appliers pending. See roadmap.
MIT
atalaya-cli es un CLI Python que:
- Agrega ofertas dev remoto desde los portales de empleo españoles que las herramientas internacionales se saltan, más los agregadores remote-first.
- Deduplica y almacena localmente (SQLite).
- Puntúa cada oferta contra tu perfil (stack, seniority, ubicación).
- Genera cartas de presentación y variantes de CV personalizadas con la API de Claude.
- Exporta shortlists a CSV/JSON para revisión manual.
- Aplica automáticamente a ofertas con email de contacto (SMTP). Rate-limited (1 envío / 5 min por defecto) para evitar detección como spam.
Todavía no está en PyPI, así que se instala desde el código:
git clone https://github.com/ElRaxy/atalaya-cli && cd atalaya-cli
python3 -m venv .venv && source .venv/bin/activate
pip install -e .Y para instalar la skill de agente en tu CLI (Claude Code, Codex, OpenCode):
bhound skill installbhound init
bhound search --board all --remote-only
bhound list --min-score 60
bhound letter <id-oferta>
bhound cv <id-oferta>
bhound apply <id-oferta> --preview # dry-run, verifica email destino
bhound apply <id-oferta> # envía email (ofertas con email contacto)
bhound apply-manual <id-oferta> # ofertas con form: copia carta, abre navegador
bhound apply-manual <id-oferta> --mark-applied # re-ejecuta para registrar APPLIED en BD
bhound apply-batch --min-score 70 --limit 5| Board | Fuente | Notas |
|---|---|---|
remoteworkspain |
remoteworkspain.es | JSON-LD JobPosting en páginas de detalle. |
jobfluent |
jobfluent.com | HTML server-side. Startups Barcelona/EU. |
himalayas |
himalayas.app (country=Spain) | Remote-first. Next.js SSR. |
indeed_es |
es.indeed.com | Bloqueado por Cloudflare en HTTP directo. Parser listo para Playwright. |
remoteok |
remoteok.com (JSON API) | Worldwide / Europe / EMEA. Filtro dev-only. |
weworkremotely |
weworkremotely.com (RSS) | 4 categorías programación. Excluye USA-only. |
tecnoempleo |
tecnoempleo.com | Dev jobs ES, filtro remoto. |
infojobs |
infojobs.net | Generalista ES. Primeras 5 cards (resto JS scroll). |
linkedin_public |
linkedin.com/jobs-guest | Sin auth. Spain + remoto (f_WT=2). ~25 ofertas/run. |
Usa --board <nombre> para uno solo o --board all para correrlos en paralelo.
Un run típico produce varios cientos de ofertas antes de scoring + dedup. De los 9 lectores
registrados responden 8, medido en cinco pasadas espaciadas el 2026-08-23. El que no responde
es himalayas, y no por el captcha: Scrapling resuelve el Turnstile y el origen sigue devolviendo
403, así que el bloqueo es del WAF. indeed_es sí vuelve a dar ofertas con el extra stealth
(16 tarjetas en las cinco pasadas).
Atalaya utiliza la API de Anthropic (Claude) para generar cartas de presentación y variantes de CV adaptadas a cada oferta. El prompt caching está activo en el system prompt para reducir coste entre llamadas sucesivas.
bhound letter 3 --lang es
bhound letter 3 --lang en --tone warm --out applications/zendrop-letter.md
bhound cv 3 --lang en --out applications/zendrop-cv.md
bhound export --fmt csv --out shortlistAtalaya soporta dos formas de hablar con Claude. El default es CLI — lanza
claude -p por subprocess y reutiliza tu suscripción Claude Code (Pro / Max /
Team) vía OAuth keychain. Sin ANTHROPIC_API_KEY, sin facturación extra.
[claude]
backend = "cli" # default — tira de tu suscripción Claude Code
model = "claude-sonnet-4-6" # también claude-haiku-4-5-20251001, claude-opus-4-7Override por llamada con ATALAYA_CLAUDE_BACKEND=api|cli.
Para usar el backend API (tu propia ANTHROPIC_API_KEY, facturación aparte):
pip install -e ".[api]"Y en config.toml:
[claude]
backend = "api"
[anthropic]
api_key = "sk-ant-..."Algunos portales están detrás de un challenge de Cloudflare y responden 403 a
un GET normal. El extra stealth añade Scrapling,
que pilota un navegador real y resuelve el challenge:
pip install -e ".[stealth]"
scrapling install # descarga los binarios de navegador (~1 GB de caché)No hay que configurar nada: los scrapers que lo aprovechan lo detectan solos y degradan al GET normal si el extra no está instalado.
| fuente | HTTP normal | con stealth |
|---|---|---|
indeed_es |
403 |
200 — funciona (16 tarjetas/página, medido 2026-08-23) |
himalayas |
403 |
403 — Turnstile "managed" + WAF, el bypass no entra |
No es dependencia del paquete base: arrastra binarios de navegador y la mayoría de fuentes no lo necesitan.
(ANTHROPIC_API_KEY env var también se reconoce.)
El comando cv lee el CV base de projects/job-search/cv/cv-{lang}.md por defecto
(relativo al directorio desde el que se ejecuta bhound). Se puede sobrescribir con la
variable de entorno ATALAYA_BASE_CV_DIR (path absoluto a la carpeta con cv-es.md /
cv-en.md) o el flag --cv-base PATH por llamada. El formato es Markdown plano —
Atalaya lo pasa textual a la API de Claude como el CV base que adaptar a cada oferta.
Atalaya puede enviar tu carta + variante CV por email a ofertas que expongan dirección de contacto.
El flujo esperado es letter → cv → apply — apply recupera la carta y el CV
persistidos en SQLite y los reutiliza. Si no se han generado para la oferta, apply emite
un warning y cae al cuerpo genérico corto sin adjunto.
bhound letter 42 # genera carta tailored (guardada en DB)
bhound cv 42 # genera CV variant (guardado en DB, preserva carta)
bhound apply 42 --preview # muestra qué pasaría, no envía
bhound apply 42 # envía de verdad (rate-limit 5 min activo)
bhound apply-batch --min-score 70 --limit 5Configuración SMTP en <config_dir>/config.toml:
[smtp]
host = "smtp.gmail.com"
port = 587
user = "tu@example.com"
password = "<gmail-app-password>" # NUNCA tu password real — usa App Password
from_name = "Alex Mico"
from_email = "tu@example.com"
starttls = trueRate-limit: persistente entre invocaciones CLI, 5 min entre applies + jitter aleatorio (±90s).
Saltable con --force (riesgo de detección).
LinkedIn, InfoJobs, Tecnoempleo y la mayoría de portales de empresa usan formulario propio (sin email expuesto). Atalaya te ayuda a aplicar a esas en ~30s por oferta sin automation contra la plataforma (cero riesgo de ban):
bhound apply-manual 42 # prepara dossier, abre navegador
# (pegas carta en form, adjuntas CV, clicas Submit tú mismo)
bhound apply-manual 42 --mark-applied # re-ejecuta para registrar APPLIED en BDQué hace:
- Recupera carta + CV variant persistidos en SQLite.
- Escribe ambos a
<tmp>/atalaya-apply-<id>/{letter.md,cv.md}(rutas en stdout). - Copia la carta al portapapeles del sistema (Windows
clip/ macOSpbcopy/ Linuxxclip/xsel/wl-copy). - Abre la URL de la oferta en tu navegador por defecto.
- Tú pegas, adjuntas el CV, clicas Submit en el navegador.
- Re-ejecutas con
--mark-appliedpara registrar la oferta como APPLIED en BD.
LinkedIn Easy Apply / InfoJobs Playwright automatizados se han descartado intencionalmente (ADR-0047, 2026-05-16). Selenium/Playwright contra anti-bot detection es frágil y supone riesgo real de ban en cuenta personal.
Atalaya es email-apply-only por diseño. Si necesitas Easy Apply LinkedIn, combina Atalaya (scoring + cartas + CVs tailored) con alguna tool externa:
- Outreach manual a recruiters — empíricamente más efectivo que Easy Apply masivo.
- Simplify.jobs (extensión Chrome gratis) — autofill formularios uno a uno.
- LazyApply (SaaS pago, ~$129) — Easy Apply masivo LinkedIn.
Funcionan de forma independiente; Atalaya no depende ni integra con ninguna.
Atalaya también lee alertas de empleo desde tu bandeja (LinkedIn / InfoJobs / Tecnoempleo / RemoteOK)
por IMAP y vuelca las ofertas a SQLite. Idempotente: cada Message-ID se registra en
email_seen — re-ejecutar no duplica.
bhound ingest-email --folder INBOX --since-days 7Configuración IMAP en <config_dir>/config.toml:
[imap]
host = "imap.gmail.com"
port = 993
user = "tu@example.com"
password = "<gmail-app-password>" # mismo App Password que usas para SMTP sirve aquí
use_ssl = trueLos 4 parsers soportados (email_linkedin, email_infojobs, email_tecnoempleo,
email_remoteok) se auto-asignan por el header From. Remitentes desconocidos se ignoran.
Alpha. 9 scrapers operativos, generadores IA funcionando, apply por email funcionando, ingesta IMAP funcionando, appliers Playwright pendientes. Ver roadmap.
MIT