job2apply
Veille quotidienne d'offres d'emploi, tri selon un profil, et preparation de candidatures adaptees (lettre de motivation ciblee + CV) soumises a validation avant tout envoi.
Fonctionnement
job2apply fetchinterroge l'API France Travail et les sources d'appoint activees (LinkedIn, Adzuna, Google Jobs), deduplique viadata/seen.json, applique un pre-filtre par mots-cles, et ecritdata/shortlist.json. Les offres retenues s'ajoutent aussi au cataloguedata/offres.json, qui les garde d'un cycle a l'autre.- Le skill Claude Code
veille-emploireprend cette shortlist, fait le vrai tri de fond, redige une lettre par offre danscandidatures/(et, si le profil declare un CV Typst, un CV adapte a l'offre), puis ouvre l'interface web pour la revue. - L'envoi reste manuel pour l'instant (pas d'auth email configuree).
job2apply ui, ou un double-clic sur le lanceur « Ouvrir job2apply » de l'espace, ouvre une page web locale pour parcourir le catalogue, relire et retoucher les lettres, et marquer chaque offre candidatee ou supprimee.
Tous ces chemins sont relatifs a un espace de travail : un dossier par recherche d'emploi, distinct du code (voir plus bas).
Installation
uv tool install job2apply # la commande job2apply, disponible partout
job2apply install-skill # le skill Claude Code veille-emploi
job2apply install-skill copie veille-emploi dans ~/.claude/skills/, ou Claude
Code le trouve depuis n'importe quel dossier. Un skill deja present et modifie
n'est pas ecrase sans --forcer, et --projet l'installe dans l'espace de travail
courant plutot que chez l'utilisateur.
uv tool install --with-executables-from playwright "job2apply[google-jobs]" ajoute la
source Google Jobs, qui pilote un navigateur via Playwright ; sans cet extra, job2apply fetch la signale en echec (Playwright manquant) si le profil l'active. Il faut
ensuite lancer une fois playwright install chromium.
Sans rien installer
uvx job2apply install-skill # pose le skill veille-emploi, sans rien installer d'autre
uvx recupere le paquet depuis PyPI a la demande, l'execute, puis jette
l'environnement : rien ne reste sur la machine hormis le skill Claude Code. La veille
tourne alors via uvx job2apply fetch, au prix d'une resolution du paquet a chaque
lancement — uv tool install reste preferable pour un usage quotidien.
Depuis les sources
Pour developper sur le depot lui-meme :
git clone <url-du-depot> job2apply
uv tool install -e ./job2apply # garde le lien avec les sources, commande disponible partout
uv sync # ou : travailler avec `uv run job2apply`, sans rien installer globalement
Puis un espace de travail :
mkdir ~/recherche-data-engineer && cd ~/recherche-data-engineer
job2apply init
init y depose config/profile.yaml et .env a remplir, les dossiers data/,
candidatures/ et cv/, et le lanceur de l'interface web (voir
Interface web). Relance sur un espace existant, il ne remplace rien.
Identifiants France Travail : creer un compte gratuit sur https://francetravail.io,
puis une application abonnee a l'API « Offres d'emploi v2 ». Le client_id et le
client_secret vont dans le .env de l'espace (jamais commite).
Espaces de travail
Un espace de travail est un dossier contenant config/profile.yaml — c'est tout ce qui
le definit. Il porte le profil, les identifiants d'API, la memoire des offres deja vues
(data/seen.json), le CV et les candidatures preparees. Deux recherches d'emploi
menees en parallele, ou deux personnes sur la meme machine, vivent dans deux espaces et
ne se marchent pas dessus.
L'outil determine a quel espace il s'applique, dans cet ordre :
- l'option
--espace <chemin>; - la variable d'environnement
JOB2APPLY_HOME— pratique pour fixer l'espace une fois pour toutes (session shell, tache planifiee) sans repeter--espacea chaque commande ; - le premier dossier parent, en partant du dossier courant, qui contient
config/profile.yaml— donc lancer la commande depuis un sous-dossier de l'espace fonctionne ; - a defaut, le dossier courant.
Le depot lui-meme peut servir d'espace : job2apply init a sa racine, et le profil ainsi
que data/ y sont deja ignores par git.
L'option globale --verbeux (comme --espace, avant la sous-commande) descend le
journal en DEBUG, y compris sur la console — voir Journal.
Configuration
Tout le filtrage et la redaction s'appuient sur config/profile.yaml : mots-cles de
recherche, localisation, types de contrat, termes bonus/eliminatoires, points forts
utilises dans les lettres. Le CV de base va dans cv/.
Aucune donnee personnelle n'est codee en dur dans le code : changer de profil (autre
metier, autre region, autre candidat) se fait entierement dans ce fichier. Les modeles
copies par job2apply init — profil, .env et commande Claude Code — vivent dans le
paquet, sous src/job2apply/espace/modeles/, pour rester disponibles une fois l'outil installe
loin du depot.
Plusieurs villes
recherche.villes accepte une liste : chaque entree porte nom (la localite en clair,
pour Adzuna, LinkedIn et Google Jobs) et commune (son code INSEE, pour France Travail).
recherche:
villes:
- nom: "Nantes"
commune: "44109"
- nom: "Paris"
commune: "75056"
distance_km: 10
Toutes les sources interrogent chaque ville, une requete par ville et par mot-cle. Une
offre remontee par plusieurs villes n'est conservee qu'une fois, attribuee a la premiere
ville de la liste qui l'a trouvee — c'est ce que lit son champ ville_recherche, affiche
dans une colonne supplementaire de la table des qu'au moins deux villes sont
configurees. distance_km, pays, departements, types_contrat et publiee_depuis
restent des reglages globaux, communs a toutes les villes.
L'ancienne ecriture a une seule ville (commune: et lieu: scalaires) reste acceptee
et vaut une ville unique.
Multiplier les villes multiplie le nombre de requetes, LinkedIn en premier lieu : voir CONTRIBUTING.md.
Interface web
job2apply ui ouvre dans le navigateur une page servie localement (127.0.0.1, port
8765 par defaut) qui liste toutes les offres retenues de l'espace, tous cycles
confondus : score, entreprise, lieu, contrat, source, lien de l'annonce ou email, et
description depliable. Chaque offre se marque Candidatee ou Supprimer (elle est
alors masquee, sans etre effacee : l'onglet « Supprimees » permet de la remettre a
traiter). Recherche texte, filtre par source et tri par score ou par date completent.
Supprimer une offre dont le dossier de candidature a ete prepare efface aussi ce
dossier (lettre, CV adapte, envoi), apres une confirmation qui le nomme : l'offre peut
etre remise a traiter, pas le dossier. job2apply statut <id> supprimee, en ligne de
commande, ne touche pas aux dossiers.
Quand /veille-emploi a prepare le dossier de candidature d'une offre, un bouton
Lettre de motivation ouvre sa lettre.md dans un editeur : on la relit, la
retouche, et l'enregistre (bouton ou Ctrl+S) directement dans le dossier. Fermer avec
des modifications non enregistrees demande confirmation. Le lien offre-dossier passe
par la ligne id: <id> que le skill ecrit dans offre.md ; l'interface ne cree pas de
lettre elle-meme.
L'onglet Profil edite config/profile.yaml : un formulaire par section (identite,
recherche et villes, tri des offres, candidature, LinkedIn, Google Jobs), et un mode
YAML brut pour le reste (journal, cles propres). Seuls les champs modifies sont
reecrits, et les commentaires, l'ordre des cles, les guillemets et les fins de ligne du
fichier sont conserves. Un profil que la veille ne saurait pas lire (aucun mot-cle,
code INSEE mal forme, YAML casse...) est refuse avec la liste des problemes, sans
toucher au fichier ; un fichier modifie ailleurs depuis l'ouverture de la page n'est pas
ecrase. Les changements valent a partir de la veille suivante.
Les statuts vivent dans data/offres.json, le catalogue que fetch et ingest
alimentent a chaque cycle : une offre qui revient garde son statut. Un espace cree avant
le catalogue le voit amorce avec sa shortlist courante. job2apply statut <id> candidatee fait la meme chose en ligne de commande (c'est ce qu'utilise le skill).
La commande s'arrete d'elle-meme quelques secondes apres la fermeture du dernier onglet
de l'interface (un simple rechargement ne la coupe pas) ; --garder-ouvert desactive
ce comportement, --sans-navigateur n'affiche que l'adresse. Relancee alors que
l'interface de ce meme espace tourne deja, elle rouvre simplement la page.
Lanceur
Pour s'en servir sans terminal, job2apply init depose a la racine de l'espace un
lanceur a double-cliquer, avec sa propre icone :
| Systeme | Lanceur |
|---|---|
| Windows | raccourci Ouvrir job2apply (la console s'ouvre reduite) |
| macOS | application Ouvrir job2apply.app |
| Linux | ouvrir-job2apply.desktop (GNOME demande une fois d'autoriser son lancement) |
Il appelle le script de lanceur/, qui lance job2apply ui sur son espace, ou
uvx job2apply ui si la commande n'est pas installee. Le raccourci Windows et l'entree
Linux retiennent des chemins absolus : apres un deplacement de l'espace, relancer
job2apply init les regenere (le reste de l'espace n'est pas touche).
La veille (job2apply fetch) et l'interface (job2apply ui) verifient aussi que le
lanceur de leur espace est la, et recreent ce qui manque (un espace cree avant le
lanceur, un raccourci efface par megarde) ; un lanceur present n'est pas reecrit. Les
autres commandes (install-skill, statut, cv...) n'y touchent pas.
CV adapte (Typst)
Si le CV est ecrit en Typst, /veille-emploi peut en preparer une
version adaptee a chaque offre, a cote de la lettre. Dans config/profile.yaml :
candidature:
cv_typst: "cv/mon_cv.typ"
polices_typst: ["cv/polices"] # optionnel, relatif a l'espace ou absolu
ignorer_polices_systeme: false
Deux commandes encadrent l'adaptation, que fait le skill :
job2apply cv preparer <dossier> # copie le CV source dans <dossier>/cv.typ
job2apply cv compiler <dossier> # produit <dossier>/cv.pdf
<dossier> est un dossier de candidature, ou son seul nom sous candidatures/.
preparer reecrit les chemins relatifs du CV (#import, image, bibliography...)
en chemins depuis la racine de l'espace, pour que la copie trouve toujours son gabarit,
ses images et sa bibliographie ; ces fichiers doivent donc vivre dans l'espace. Un
cv.typ deja present n'est pas ecrase sans --forcer. En cas d'erreur, compiler
affiche le diagnostic Typst (fichier, ligne, extrait).
La compilation passe par le paquet PyPI typst, sans binaire a installer, via l'extra
cv : uv tool install "job2apply[cv]", uvx --from "job2apply[cv]" job2apply ..., ou
uv sync --extra cv dans le depot.
Adzuna (source d'appoint)
Adzuna est un agregateur d'offres avec une API publique et une inscription libre sur
https://developer.adzuna.com : l'app_id et l'app_key sont delivres immediatement et
vont dans .env. La source est optionnelle — sans identifiants, job2apply fetch la
saute sans rien signaler.
Elle se pilote par les memes cles de config/profile.yaml que France Travail :
mots_cles, villes, distance_km, types_contrat, publiee_depuis. Une cle lui est
propre, car Adzuna ne connait pas le code INSEE : pays (fr par defaut). Chaque ville
lui est passee par son nom en clair (par defaut identite.ville), pas son commune.
Adzuna ne publie pas d'email de contact : la candidature passe par l'URL de l'annonce.
LinkedIn (source d'appoint)
LinkedIn n'ouvre plus son API d'offres aux particuliers, mais sa recherche reste consultable sans compte : c'est l'espace « invite », que la source lit directement en HTTP, sans identifiants ni navigateur. C'est du scraping (voir CONTRIBUTING.md), d'ou l'activation explicite.
Elle est donc opt-in, dans config/profile.yaml :
linkedin:
actif: true
max_offres: 50 # par mot-cle ; jusqu'a 60, une seule requete suffit
lire_descriptions: true
delai_s: 1.0
langue: "fr-FR"
Elle reprend mots_cles, villes, distance_km et publiee_depuis de la section
recherche. types_contrat ne lui est pas transmis : le filtre de LinkedIn porte sur
le rythme de travail (temps plein, stage...) et non sur le CDI / CDD francais, et
l'appliquer ecarterait des offres valables. Le champ contrat reprend donc ce rythme
tel quel, sauf pour les quelques cas qui se recoupent (stage, alternance, interim).
Compte environ deux minutes au premier lancement, puis une vingtaine de secondes les
jours suivants, une fois les offres deja vues memorisees ; lire_descriptions: false
raccourcit encore la veille, au prix du tri automatique. Le detail du rate-limiting
mesure et des mecanismes qui le tiennent bas est dans
CONTRIBUTING.md.
LinkedIn ne publie pas d'email de contact : la candidature part de la page de l'offre.
Google Jobs (source d'appoint)
L'onglet « Emplois » de la recherche Google agrege les offres de la plupart des jobboards francais, sans compte ni cle d'API. Il n'existe en revanche aucune API pour l'interroger — la Cloud Talent Solution sert aux employeurs qui indexent leurs propres postes — donc la source lit la page de resultats avec un vrai navigateur, pilote par Playwright. Deux consequences a connaitre avant de l'activer :
- Une fenetre de navigateur s'ouvre. Google exige JavaScript depuis 2025, et
reconnait un navigateur headless : en mode invisible, il sert son CAPTCHA des la
premiere requete. Le profil navigateur est conserve dans
data/google-profile/pour ne repondre qu'une fois a la banniere de consentement. - C'est du scraping, contrairement aux autres sources, d'ou l'activation explicite ;
voir CONTRIBUTING.md pour le
detail (CGU,
robots.txt, risque de CAPTCHA).
Elle est donc opt-in, dans config/profile.yaml :
google_jobs:
actif: true
max_offres: 30
headless: false
langue: "fr"
Installation : voir la section Installation plus haut (--with-executables-from playwright), ou uv sync --extra google-jobs dans le depot, puis playwright install chromium une fois. Avec actif: false, job2apply fetch saute simplement la source ;
active sans Playwright, elle echoue avec [Google Jobs] echec : Playwright manquant.
Elle reprend les cles mots_cles, villes, pays et publiee_depuis de la section
recherche. Chaque offre est ouverte pour en lire le descriptif complet, ce qui permet
au scoring de trancher comme sur les autres sources ; le champ via retient le
jobboard d'origine, et url_postulation pointe directement vers lui. Google ne decrit
pas le contrat en CDI / CDD mais en rythme de travail (« A plein temps »,
« Prestataire ») : le champ contrat reprend donc son libelle tel quel.
Pourquoi pas Indeed
Indeed n'a plus d'API de recherche d'offres accessible, cote grand public comme cote
connecteur MCP : voir CONTRIBUTING.md pour le
detail des pistes explorees. Si Indeed rouvre un jour, la source se rebranche via
job2apply ingest --source indeed sans toucher au reste du code.
Ajouter une source
job2apply ingest --source <nom> lit une liste d'offres JSON sur stdin et les injecte
dans le pipeline, avec un identifiant et un titre pour seuls champs requis (voir
CONTRIBUTING.md pour le format complet et le
patron a suivre pour une source interrogee a chaque veille).
Usage
job2apply init [chemin] # cree un espace de travail
job2apply install-skill # installe le skill veille-emploi dans Claude Code
job2apply fetch # veille complete, dans l'espace courant
job2apply --espace ~/autre-recherche fetch # veille d'un autre espace
job2apply ui # interface web de suivi des offres
job2apply statut <id> candidatee # noter une offre (nouvelle, candidatee, supprimee)
job2apply cv preparer <dossier> # CV Typst a adapter pour une candidature
job2apply cv compiler <dossier> # ... puis compile en PDF
Puis, dans Claude Code lance depuis l'espace : /veille-emploi.
Sans installation globale : uv run job2apply fetch depuis le depot clone, ou
uvx job2apply fetch sans depot du tout.
Journal
Le tableau et les resumes affiches par job2apply ne changent pas ; en parallele, un
journal de diagnostic s'ecrit dans data/job2apply.log (rotation a 1 Mo, 3 archives) :
requetes envoyees a chaque source, limitations de debit de LinkedIn, tracebacks des
sources en echec. Les anomalies (niveau WARNING et au-dessus) sont aussi rappelees sur
la console.
job2apply --verbeux <commande> descend les deux canaux en DEBUG. La section logs de
config/profile.yaml permet un reglage plus fin (niveau du fichier, niveau de la
console, chemin du fichier, ou desactivation complete) ; voir les commentaires du
modele. --verbeux l'emporte toujours sur le profil.
Tests et verification
uv run pytest # tests (ajouter -m reseau pour interroger vraiment LinkedIn et Google Jobs)
uv run ruff format . # formatage
uv run ruff check . # lint
uv run ty check # types
Voir CONTRIBUTING.md pour la portee des tests reseau, et
CONTRIBUTING.md pour le detail des regles de lint
retenues.
Desinstallation
uv tool uninstall job2apply # l'executable et son environnement
rm -r ~/.claude/skills/veille-emploi # le skill Claude Code
Installee via uvx (aucun uv tool install), il n'y a pas d'outil a desinstaller : seul
rm -r ~/.claude/skills/veille-emploi s'applique. uv cache prune libere l'espace pris
par les environnements que uvx a mis en cache.
L'installation ne pose que ces deux elements. En mode editable (uv tool install -e),
elle ne contient aucune copie du code : le depot reste intact et uv run job2apply y
fonctionne toujours.
Les espaces de travail survivent volontairement — ce sont des donnees, pas du logiciel :
profil, identifiants, memoire des offres vues et candidatures preparees. Les supprimer
est une decision separee, dossier par dossier. Le lanceur (lanceur/ et « Ouvrir
job2apply ») en fait partie : il s'efface avec l'espace.
Release files for job2apply 1.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| job2apply-1.3.1.tar.gz | 254.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| job2apply-1.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 456.1 kB
Release files / job2apply-1.3.1.tar.gz
| Download URL | job2apply-1.3.1.tar.gz |
|---|---|
| Size | 254.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e8f3e235a57ecf1dcb5dd05486a3568ef951f023a5b3172d7f7d7dcb042c0f06
|
|
BLAKE2b-256 checksum How to use checksums |
a8207126f7c2a0e9d26d4cf6961973cfc7db9c9365b0b7c52f8b2ec4ef3267e0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.12.12
|
Release files / job2apply-1.3.1-py3-none-any.whl
| Download URL | job2apply-1.3.1-py3-none-any.whl |
|---|---|
| Size | 201.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a9efb50769f516c781c5d1b2749ffb772eb353d7d2485b14101ac5bd9393e219
|
|
BLAKE2b-256 checksum How to use checksums |
149c97d23476ee69ca00832f3a71695a1f1da85c59a2b0d7cdab54dc0e87d88b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.12.12
|