Metadata-Version: 2.5
Name: job2apply
Version: 2.2.0
Summary: Veille d'offres d'emploi et preparation de candidatures adaptees
Project-URL: Repository, https://gitlab.com/NyxHemera/job2apply
Project-URL: Issues, https://gitlab.com/NyxHemera/job2apply/-/issues
Author-email: NyxAether <contact.nyxhemera@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: candidature,cli,emploi,france-travail,offres,veille
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: French
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business
Requires-Python: >=3.11
Requires-Dist: beautifulsoup4>=4.12
Requires-Dist: claude-agent-sdk
Requires-Dist: cyclopts>=4.25.2
Requires-Dist: jinja2>=3.1
Requires-Dist: markdown-it-py>=3.0
Requires-Dist: python-dotenv>=1.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: requests>=2.32
Requires-Dist: rich>=15.0.0
Requires-Dist: ruamel-yaml>=0.19.1
Requires-Dist: typst>=0.15.0
Provides-Extra: cv
Provides-Extra: google-jobs
Requires-Dist: playwright>=1.44; extra == 'google-jobs'
Description-Content-Type: text/markdown

# job2apply

Veille quotidienne d'offres d'emploi, tri selon un profil, et preparation a la demande
de candidatures adaptees (lettre de motivation ciblee + CV) soumises a validation avant
tout envoi.

## Fonctionnement

1. `job2apply fetch` interroge l'API France Travail et les sources d'appoint activees
   (LinkedIn, Adzuna, Google Jobs ; France Travail et Adzuna se coupent par leur cle
   `actif: false` du profil), deduplique via `data/seen.json`, applique un
   pre-filtre par mots-cles, et ecrit `data/shortlist.json`. Les offres retenues
   s'ajoutent aussi au catalogue `data/offres.json`, qui les garde d'un cycle a l'autre.
2. Le skill Claude Code `veille-emploi` reprend cette shortlist, fait le vrai tri
   de fond (les offres ecartees passent en « supprimee »), puis ouvre l'interface web
   pour la revue. Il ne prepare aucune candidature d'office.
3. `job2apply ui`, ou un double-clic sur le lanceur « Ouvrir job2apply » de l'espace,
   ouvre une page web locale pour parcourir le catalogue, choisir les offres pour
   lesquelles generer un dossier de candidature (lettre, envoi et, si le profil declare
   un CV Typst, CV adapte), relire et retoucher les lettres, et marquer chaque offre
   candidatee ou ecartee.
4. L'envoi reste manuel pour l'instant (pas d'auth email configuree).

Tous ces chemins sont relatifs a un *espace de travail* : un dossier par recherche
d'emploi, distinct du code (voir plus bas).

## Installation

```bash
uv tool install job2apply    # la commande job2apply, disponible partout
```

Rien d'autre a installer cote Claude Code : `job2apply init` pose le skill
`veille-emploi` dans l'espace de travail (voir [Skill Claude Code](#skill-claude-code)).

`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

```bash
uvx job2apply init ~/recherche   # cree l'espace, skill veille-emploi comprise
```

`uvx` recupere le paquet depuis PyPI a la demande, l'execute, puis jette
l'environnement : rien ne reste sur la machine hormis l'espace de travail. 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. Le skill de
l'espace suit la version que `uvx` execute (voir [Skill Claude Code](#skill-claude-code)).

### Depuis les sources

Pour developper sur le depot lui-meme :

```bash
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 :

```bash
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/`, le lanceur de l'interface web (voir
[Interface web](#interface-web)) et le skill Claude Code
`.claude/skills/veille-emploi/SKILL.md`. Relance sur un espace existant, il ne remplace
ni le profil ni `.env`.

## Skill Claude Code

Le skill `veille-emploi` vit dans l'espace, sous `.claude/skills/`. Claude Code le
trouve quand il est ouvert dans l'espace (ou un sous-dossier) : c'est la que se lance
`/veille-emploi`.

Il est derive de la version de job2apply qui tourne : chaque commande lancee dans un
espace (`fetch`, `ui`, `statut`, `cv`...) le reecrit s'il differe de ce qu'elle
produirait, et l'annonce sur stderr (`skill veille-emploi mise a jour (1.7.1 -> 1.8.0)`).
Apres un `uv tool upgrade`, ou quand `uvx` prend une nouvelle version, le skill suit
sans rien faire. Une copie retouchee a la main est donc remplacee : c'est un fichier
genere, pas un fichier a adapter. `job2apply install-skill` fait la meme
synchronisation explicitement (`--projet` et `--forcer` sont acceptes, sans effet).

Le skill accorde lui-meme, par son champ `allowed-tools`, les outils de la veille :
toutes les commandes job2apply sous leurs formes d'appel usuelles (`job2apply`,
`uvx job2apply`, `uvx --from "job2apply[cv]" job2apply`, `uv run job2apply`,
`.venv/Scripts/job2apply`, `.venv/bin/job2apply`, et le chemin de l'executable qui l'a
ecrit), en Bash comme en PowerShell, et la lecture des annonces sur les domaines des
sources (France Travail, LinkedIn, Adzuna, Google). Une veille, interactive ou
planifiee, se deroule ainsi sans invite de permission ni reglage de Claude Code. Aucun
joker ne remplace le nom du programme : `uvx *` autoriserait n'importe quel paquet.

Veille planifiee : lancer `claude -p "/veille-emploi"` depuis l'espace (tache cron,
planificateur de taches Windows).

job2apply n'ecrit jamais dans `~/.claude/`. Une ancienne version installait le skill
dans `~/.claude/skills/veille-emploi` : cette copie personnelle l'emporte sur celle de
l'espace et la masque. Chaque commande la signale (l'interface aussi, par un bandeau),
sans y toucher ; `job2apply install-skill --retirer-personnelle` la supprime, tout
comme `rm -r ~/.claude/skills/veille-emploi`.

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 :

1. l'option `--espace <chemin>` ;
2. la variable d'environnement `JOB2APPLY_HOME` — pratique pour fixer l'espace une fois
   pour toutes (session shell, tache planifiee) sans repeter `--espace` a chaque
   commande ;
3. 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 ;
4. 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](#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 skill 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).

```yaml
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](CONTRIBUTING.md#le-nombre-de-requetes-est-la-ressource-rare-linkedin).

## Mise a jour depuis la 2.0

Au premier lancement de toute commande job2apply, un profil de la 2.0 est migre une seule
fois, et la commande l'annonce sur la sortie d'erreur :

- le `ton` et la `longueur_lettre` du profil passent dans les Regles de la lettre (en
  fin de `config/regles_lettre.md`, sous « Ton et longueur (repris de votre profil) »),
  sauf s'ils valent encore ceux de l'ancien modele ;
- l'`identite` du profil (nom, titre, e-mail, telephone, ville, site) passe dans le CV de
  base `cv/cv.yaml` pour les champs que celui-ci laisse vides, et part du profil ; sans CV
  de base, elle y reste jusqu'a son enregistrement dans le parcours guide de Mon CV ;
- la ville de l'identite, qui servait de ville de recherche par defaut, est recopiee
  dans les villes de `recherche.villes` qui n'ont pas de nom.

L'ancien profil est garde dans `config/.versions/profile.yaml/` (version « migration »),
comme les anciennes versions des regles et du CV dans leurs propres `.versions/` : revenir
en arriere se fait en recopiant le fichier.

## Interface web

`job2apply ui` ouvre dans le navigateur une page servie localement (`127.0.0.1`, port
8765 par defaut). Pages :

- **Offres** : les offres retenues de l'espace, tous cycles confondus, en quatre
  onglets : **A traiter**, **Dossier prepare**, **Candidatees**, **Ecartees**. Chaque
  carte montre le score, les motifs du score, les metadonnees et l'avancement du dossier
  (lettre, CV, envoi). **Ecarter l'offre** est immediat, sans confirmation, et
  annulable par le message qui suit ; il garde le dossier. **Effacer le dossier...** est
  une action distincte, avec une confirmation qui le nomme. **Remettre a traiter**
  ramene une offre ecartee ou candidatee dans l'onglet A traiter.
- **Offre** : une page par offre, en onglets **Offre**, **Lettre**, **CV adapte**,
  **Envoi** et **Historique**. L'en-tete porte l'action principale (**Preparer le
  dossier**, puis **Completer le dossier** s'il manque une piece) ; l'onglet Envoi dit ou
  postuler, les pieces a joindre a copier ou telecharger et le message
  d'accompagnement ; l'Historique remplace l'ancienne page Versions : une chronologie
  des versions de la lettre et du CV (comparer, revenir a une version) et des
  preparations de l'IA, avec leur compte rendu.
- **Lettre** : la lettre de motivation de l'offre dans un editeur, enregistree
  directement dans le dossier (bouton ou Ctrl+S) ; l'IA est un bandeau (Ctrl+K) dont la
  proposition se relit en differences ajoutees et retirees avant d'etre acceptee.
- **CV adapte** : l'editeur du CV a gauche, son apercu recompile a la volee a droite
  (un clic sur un element de l'apercu ouvre son champ), des elements deplacables a la
  souris ou au clavier ; les propositions de l'IA (une puce, l'accroche ou tout le CV)
  arrivent comme ecarts a accepter ou refuser un par un ; export PDF ; un mode libre
  pour editer le source Typst.
- **Regles** : deux editeurs, `config/regles_lettre.md` et `config/regles_cv.md`, qui
  donnent le style attendu a l'IA (un defaut du paquet s'applique tant que le fichier
  manque). Chacun s'ouvre en apercu avec un badge « Regles par defaut » ou
  « Personnalisees » ; **Modifier** ouvre l'edition, le retour au defaut se fait dans le
  brouillon et s'enregistre comme une version. Le ton et la longueur de la lettre se
  reglent ici, et seulement ici. `job2apply init` cree les deux fichiers.
- **Mon CV** : le CV de base, ecrit au gabarit de l'application (`cv/cv.yaml`, avec son
  champ **Ville**, facultatif). Sans CV, un parcours guide le cree a partir de documents
  (PDF ou texte) deposes dans `cv/sources/` ; un fichier Word est refuse. C'est lui que
  l'IA adapte a chaque offre ; son nom et ses coordonnees signent aussi la lettre. Son
  onglet **Gabarit** permet d'utiliser sa propre mise en page Typst (voir plus bas).
- **Profil** : voir plus bas.

La generation (dossier, lettre, adaptation du CV) passe par l'Agent SDK de Claude Code,
sans `claude -p` ni skill. Il faut etre connecte a Claude Code (`claude auth login`) ; si
l'agent est indisponible, un bandeau en donne la cause (celle de `claude auth status`) et
les boutons IA sont grises. Le CLI de Claude Code est embarque dans l'installation, qui
est donc plus lourde (environ 257 Mo) ; `claude-agent-sdk` et `typst` sont des
dependances de base, l'extra `cv` est vide (conserve pour la compatibilite).

**Vie privee.** Une fois par zone IA, l'interface dit ce qui part vers Claude : l'annonce
de l'offre, les points forts du profil (et non plus le profil entier), le CV de base ou
adapte et les regles de redaction. Rien d'autre
de l'espace n'est lu, et les sessions ne sont pas conservees
(`--no-session-persistence`). Le `.env` est hors de portee de l'agent ; cette garantie a
ete verifiee sous Windows seulement.

**Modele.** `sonnet` par defaut ; le changer par la cle `ia.modele` du profil, dans le
mode YAML brut de la page Profil (pas de champ de formulaire).

L'onglet **Profil** edite `config/profile.yaml` : un seul formulaire, en cinq sections
(**Recherche**, **Tri des offres**, **Sources**, **Candidature**, **Avance**), et un mode
**YAML brut** pour le reste (journal, cles propres, `ia.modele`). Il n'y a plus de
section Identite : le nom et les coordonnees vivent dans Mon CV. Dans Tri des offres, le
score minimum se regle au curseur et une simulation dit combien d'offres de la derniere
veille seraient retenues avec ces reglages. Dans Sources, chaque source a son
interrupteur : France Travail (`france_travail.actif`) et Adzuna (`adzuna.actif`), actives
par defaut, disent si leurs identifiants sont trouves (jamais leur valeur) ; LinkedIn et
Google Jobs restent opt-in. Si aucune source n'est active, `job2apply fetch` s'arrete
avec le code 1 sans rien ecrire. 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.

### Gabarit Typst personnel

L'onglet **Gabarit** de Mon CV branche votre propre CV Typst sur l'editeur, l'apercu
cliquable, le PDF de Mon CV et les CV adaptes a chaque offre. **Importer mon gabarit
Typst** : deposez le dossier de votre CV, ou ses fichiers (un `.typ` seul est admis), puis
designez le fichier qui est votre CV d'exemple. L'IA ecrit alors un **adaptateur**, une
fonction `cv(donnees)` qui appelle votre code de style sans le reecrire (sauf pour un
`.typ` seul : c'est un document complet, son style est recopie dans l'adaptateur, et
l'ecran le dit). Vous comparez votre CV d'exemple (a gauche) a l'apercu avec l'adaptateur
(a droite) avant d'enregistrer ; rien n'est installe avant.

- Le gabarit vit dans `cv/gabarit-perso/` de l'espace, un seul a la fois ; un nouveau
  depot remplace l'ancien, qui part dans `cv/.corbeille/`. L'adaptateur
  (`adaptateur.typ`) a un historique de versions comme le CV ; une retouche a la main est
  figee avant tout redepot. Ce que l'application ecrit elle-meme est sous `_job2apply/`.
- Les noms de fichiers sont gardes tels quels (arborescence, espaces, accents). Sont
  refuses, avec leur raison : `..`, chemins absolus, noms caches, noms reserves de Windows,
  `.wasm`, extensions inconnues, fichiers trop gros (100 fichiers, 5 Mo chacun, 200 Ko par
  `.typ`) et tout chemin absolu dans un `import`, `include` ou `image`.
- Le schema du CV ne change pas. Ce qui ne s'y place pas (liens supplementaires,
  `keypoint`...) est liste « non place dans cv.yaml » et **laisse tel quel dans le code**
  de l'adaptateur. Un controle de couverture liste le texte de votre CV d'exemple absent
  du rendu ; il ne bloque jamais l'enregistrement.
- **Revenir au gabarit integre** est immediat et reversible (**Reutiliser mon gabarit**).
  Un CV d'offre au gabarit personnel **importe l'adaptateur** : son `cv.typ` n'est plus
  autonome, et redevient integre si on revient au gabarit de job2apply.
- Limites. L'apercu cliquable est exact pour une colonne, approximatif pour des colonnes
  ou une barre laterale. Un paquet `@preview/...` importe par votre gabarit est telecharge
  par Typst a la compilation (acces reseau du compilateur, pas de job2apply) : l'apercu
  peut echouer hors ligne ; l'adaptateur, lui, ne peut pas en importer.
- Securite. Les fichiers deposes sont du code que Typst execute dans la racine de l'espace,
  sans controle (comme le mode libre). Seul l'adaptateur, ecrit par l'IA, subit un controle
  **statique, hors chaines et commentaires** : identifiants interdits (`read`, `eval`,
  `plugin`, `json`, `yaml`, `sys`...), un seul chemin litteral relatif pour `import`,
  `include` et `image`. Ce controle a des limites connues : un guillemet de markup non
  apparie (`[ " ]`) fait lire une chaine qui n'en est pas une, et ce qu'elle cache n'est pas
  scanne (il faudrait un `CV.typ` piege par vous-meme) ; et `pdf.embed` n'est pas interdit,
  donc un adaptateur peut embarquer dans le PDF un fichier de l'espace.
- Vie privee : les fichiers `.typ` du gabarit et du CV d'exemple et votre consigne sont
  envoyes a Claude (Anthropic) ; les images et les polices restent sur le poste, et ni le
  profil ni les regles de redaction ne sont envoyes.

### 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](https://typst.app), `/veille-emploi` peut en preparer une
version adaptee a chaque offre dont on demande la candidature, a cote de la lettre. Dans `config/profile.yaml` :

```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 :

```bash
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`. `compiler --fichier <nom.typ>`
compile un autre fichier du dossier, tel quel (le PDF porte le meme nom). Si le dossier
a un `cv.yaml`, `compiler` reecrit d'abord `cv.typ` d'apres la source de rendu active
(gabarit integre ou personnel) : une retouche manuelle de `cv.typ` est ecrasee. Sans
`cv.yaml`, `cv.typ` est compile tel quel, avec les polices du gabarit personnel s'il est
actif. En cas d'erreur,
`compiler` affiche le diagnostic Typst (fichier, ligne, extrait).

La compilation passe par le paquet PyPI `typst`, sans binaire a installer : c'est une
dependance de base, rien a ajouter. L'extra `cv` existe encore mais est vide (les formes
`job2apply[cv]` restent valables).

Avec un gabarit Typst personnel actif (onglet Gabarit de Mon CV), le CV adapte d'une offre
n'est plus un fichier autonome : son `cv.typ` importe l'adaptateur de l'espace
(`cv/gabarit-perso/adaptateur.typ`) et se recompile a partir du `cv.yaml` de l'offre.

## 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. France Travail se coupe de la meme facon par
`france_travail.actif: false`.

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, pas son `commune` ; une ville sans `nom` n'ajoute
aucun filtre de lieu. La source se coupe aussi par `adzuna.actif: false`.

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](CONTRIBUTING.md#scraping--linkedin-et-google-jobs)), d'ou
l'activation explicite.

Elle est donc opt-in, dans `config/profile.yaml` :

```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](CONTRIBUTING.md#le-nombre-de-requetes-est-la-ressource-rare-linkedin).

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](CONTRIBUTING.md#scraping--linkedin-et-google-jobs) pour le
  detail (CGU, `robots.txt`, risque de CAPTCHA).

Elle est donc opt-in, dans `config/profile.yaml` :

```yaml
google_jobs:
  actif: true
  max_offres: 30
  headless: false
  langue: "fr"
```

Installation : voir la section [Installation](#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](CONTRIBUTING.md#pourquoi-pas-indeed) 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](CONTRIBUTING.md#ajouter-une-source) pour le format complet et le
patron a suivre pour une source interrogee a chaque veille).

Pour ajouter a la main une offre que la veille a laissee passer (deja vue, ou sous le
seuil de score), `job2apply ingest --source <nom> --forcer` la retient quand meme :
elle rejoint la shortlist et l'onglet « A traiter » de l'interface.

## Usage

```bash
job2apply init [chemin]                      # cree un espace de travail
job2apply install-skill                      # remet a jour le skill veille-emploi de l'espace
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

```bash
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](CONTRIBUTING.md#tests) pour la portee des tests `reseau`, et
[CONTRIBUTING.md](CONTRIBUTING.md#verification) pour le detail des regles de lint
retenues.

## Desinstallation

```bash
uv tool uninstall job2apply                    # l'executable et son environnement
```

Le skill vit dans chaque espace (`.claude/skills/veille-emploi`) et part avec lui. Une
copie personnelle laissee par une ancienne version se retire par
`rm -r ~/.claude/skills/veille-emploi`. Installee via `uvx` (aucun `uv tool install`),
il n'y a pas d'outil a desinstaller. `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.
