Guide utilisateur
Modifier cette page sur GitHubAide — career-ops-ui
Une présentation complète de chaque page, du moment où vous lancez
l’application jusqu’à l’obtention d’un entretien. Chaque titre ##
ci-dessous correspond à une entrée de la barre latérale ou à une phase
du flux de travail. Lisez de haut en bas au premier lancement ; revenez
plus tard à une section précise via la table des matières dans la barre
latérale de l’aide.
Public visé : toute personne qui vient de déposer cette UI dans un checkout
career-opset a lancébash bin/start.sh. Aucune connaissance préalable de career-ops n’est supposée.
About career-ops
career-ops est un système open-source de recherche d’emploi qui s’exécute sous forme de commandes slash dans n’importe quel CLI de codage IA (Claude Code, Gemini CLI, Codex, Qwen Code, OpenCode, GitHub Copilot CLI — d’autres CLI compatibles Claude fonctionnent aussi via la même surface de commandes slash). Indépendant du modèle. Il évalue chaque offre par rapport à votre CV selon une grille à six dimensions notée de 0.0 à 5.0, génère des CV PDF sur mesure, et suit chaque candidature localement sur votre machine.
Référence canonique (à lire dans l’ordre à la première installation) :
- What is career-ops — le système, ses principes et l’inventaire des concepts.
- Scan job portals — découvrir des offres ; alimenter le Pipeline.
- Apply for a job — flux de soumission complet avec lecture de formulaire Playwright.
- Batch-evaluate offers
— noter 10+ offres d’un coup via
batch-runner.sh. - Set up Playwright — installer Chromium + enregistrer le MCP pour le PDF et le remplissage de formulaires.
Principes fondateurs (d’après career-ops.org/docs/introduction/what-is-career-ops) :
- Open source, pour de vrai — MIT, pas de palier payant, pas de liste d’attente, pas de télémétrie, pas de comptes. Le système fonctionne sans paliers payants, sans comptes ni télémétrie. Les contributions de code passent par une revue communautaire avant publication.
- Souveraineté des données —
cv.md,config/profile.yml,data/,reports/,interview-prep/ne quittent jamais votre ordinateur sauf si vous les poussez explicitement. Vous l’exécutez localement sur votre machine, en conservant une pleine souveraineté des données. - Architecture agnostique vis-à-vis de l’IA — career-ops n’embarque PAS de modèle. Il fonctionne comme des commandes au sein de CLI de codage IA existants. Changez de fournisseur (Anthropic ↔ Gemini ↔ OpenAI) et votre historique d’évaluations reste cohérent.
- Soumissions contrôlées par l’humain — career-ops rédige les réponses et ouvre le formulaire, mais c’est vous qui cliquez sur Envoyer. Le système ne postule jamais automatiquement. Il fournit la structure et l’évaluation ; l’humain garde l’autorité finale de soumission.
- Recherche structurée — conçu pour une recherche d’emploi active et délibérée avec de nombreuses candidatures ; ce n’est pas un outil de soumission unique, ni un moteur de recommandation. La configuration prend ~15 minutes et suppose une aisance avec le terminal.
Ce que career-ops N’EST PAS (non-objectifs explicites) :
- Pas un auto-postulateur. Il ne soumettra pas les formulaires à votre place.
- Pas un reconstructeur de CV. Il adapte par offre ; il n’invente pas d’expérience.
- Pas un optimiseur LinkedIn. Votre profil vous regarde.
- Pas un substitut de tableur caché derrière une UI SaaS. Les données sont du markdown brut sur votre système de fichiers.
Concepts clés (inventaire complet — chaque artefact que career-ops touche) :
| Concept | De quoi il s’agit |
|---|---|
| Mode | Un modèle de prompt sous modes/<slug>.md. Intégrés : oferta, deep, apply, pipeline, batch, contacto, followup, interview-prep, patterns, project, training, ofertas, auto-pipeline, pdf, latex, scan, tracker. |
| Archetype | Un profil de poste cible dans config/profile.yml. La grille pondère les correspondances de compétences par rapport à l’archétype actif — le champ le plus important. |
| Pipeline | data/pipeline.md — boîte de réception des URL d’offres en attente d’évaluation. |
| Tracker | data/applications.md — tableau GFM historique de chaque évaluation + statut de candidature. |
| Report | reports/<NNN>-<company>-<DATE>.md — évaluation A–F complète par offre, avec score + légitimité dans l’en-tête. |
| Scan history | data/scan-history.tsv — journal en ajout seul ; évite les doublons entre scans. |
| Proof points | Blocs de preuves STAR+R extraits de cv.md, réutilisés dans l’évaluation, les réponses de candidature et la préparation d’entretien. |
| JD store | jds/jd-<date>-<ts>.txt — descriptions de poste verbatim sauvegardées lors de l’évaluation pour la traçabilité. |
| Interview-prep | interview-prep/<company>-<role>.md — dossiers de recherche approfondie et fiches récapitulatives par tour. |
| Batch additions | batch/tracker-additions/*.tsv — lignes en attente mises en file par batch-runner.sh pour fusion dans le tracker. |
career-ops vs career-ops-ui (this app)
| career-ops (CLI) | career-ops-ui (cette application) | |
|---|---|---|
| Où il s’exécute | dans Claude Code / Gemini CLI / Codex / Qwen Code / OpenCode / GitHub Copilot CLI | http://127.0.0.1:4317 dans votre navigateur |
| Surface | commandes slash /career-ops <mode> |
barre latérale avec une page par flux de travail |
| Remplissage de formulaire | oui, via Playwright MCP | non — génère la checklist, vous terminez dans le CLI |
generate-pdf.mjs |
📄 Generate PDF sur #/cv, #/reports/:slug, #/evaluate, #/deep, #/interview-prep |
|
| Fichiers de données | partagés avec career-ops-ui | partagés avec career-ops |
career-ops-ui n’apporte que des ajouts. Rien à l’intérieur de
career-ops/ ne change. Les deux surfaces partagent les mêmes cv.md,
config/profile.yml, portals.yml, data/, reports/,
interview-prep/, modes/.
Action thresholds by score
Une fois qu’une offre a une évaluation, le score détermine la suite (tableau canonique de career-ops.org/docs/introduction/what-is-career-ops) :
| Score | Étape suivante |
|---|---|
| ≥ 4.5 | Lancez /career-ops apply — forte adéquation, foncez tout de suite. |
| 4.0 – 4.4 | Postulez, ou /career-ops contacto pour une intro chaleureuse d’abord. |
| 3.5 – 3.9 | Lancez /career-ops deep — recherchez l’entreprise / le poste avant de décider. |
| < 3.5 | Passez, sauf raison personnelle précise. |
Le #/dashboard et le #/tracker de career-ops-ui mettent en évidence
chaque ligne à 4.0 ou au-dessus pour que vous puissiez choisir une action
sans rien relancer.
External docs
La référence complète du moteur career-ops sous-jacent (scan, grille d’évaluation, traitement par lot, flux de candidature, configuration Playwright) est sur career-ops.org/docs :
1. Quick start — pas à pas complet, de « créer le CV » à « postulé & contacté »
Voici le manuel canonique, bouton par bouton. Suivez-le dans l’ordre la première fois. Chaque étape nomme la route exacte, le bouton exact, et ce que vous verrez en cas de succès. Les sections 2 à 16 ci-dessous détaillent chaque phase.
Demander au guide. Ouvrez Demander au guide 💬 (barre latérale, sous Aide) et posez une question — il répond uniquement depuis ce guide dans votre langue et ne lit jamais votre CV. Le même assistant est à un clic depuis chaque page : un bouton de chat robot flotte dans le coin inférieur droit (inférieur gauche dans les langues de droite à gauche) ; cliquez pour poser une question sans quitter ce que vous faites.
Lancement & init en une commande. Depuis un terminal, vous pouvez faire tout le bootstrap sans toucher à l’UI :
career-ops-ui setup # install deps → doctor → run the server career-ops-ui init # pick LLM provider + paste its key (echo suppressed) career-ops-ui doctor # re-verify any time (exit 0 ⇔ all required green) career-ops-ui run # just launch the server at http://127.0.0.1:4317 career-ops-ui open # open + RAISE the dashboard tab in your browserAprès
setup/run, l’onglet du navigateur est ouvert et mis au premier plan automatiquement (v1.43.0) ;career-ops-ui openfait de même à la demande, pour ne jamais avoir à chercher l’onglet du tableau de bord.NO_OPEN=1désactive l’ouverture auto pour les démarrages headless/CI.
setupexécute toute la chaîne lui-même.initécrit la clé dans lecareer-ops/.envparent via le même chemin validé que l’onglet clés-API de#/config, et définitLLM_PROVIDER(auto|claude|gemini) que respectent les routes live evaluate / deep / mode / auto-pipeline. Forme CI :career-ops-ui init --provider claude --anthropic-key sk-ant-… --yes. Vous préférez l’UI ? Continuez avec les étapes ci-dessous.
A. Setup (à faire une fois, ~5 minutes)
career-ops-ui doit se trouver à career-ops/web-ui/ (imbriqué dans le projet career-ops parent). Il lit vos cv.md, config/ et data/ depuis le dossier parent via ../ et ne fonctionne pas de manière autonome. Si career-ops-ui init n’est pas trouvé après un pull, exécutez cd career-ops/web-ui && npm install && npx career-ops-ui init.
Étape 1 — Ouvrez l’application sur http://127.0.0.1:4317. Si elle ne
tourne pas, dans un terminal lancez bash bin/start.sh depuis la racine du
dépôt. Le Tableau de bord (#/dashboard) se charge.
Étape 2 — Cliquez sur ❤ Health dans la barre latérale gauche. Chaque
vérification requise doit être verte :
cv.md,config/profile.yml,portals.ymlexistent- Clé API définie (au moins l’une de
ANTHROPIC_API_KEY/GEMINI_API_KEY) - Playwright installé (requis uniquement si vous utilisez Generate PDF)
Si quelque chose est rouge, la page vous indique le fichier ou la variable d’environnement exacte à corriger. Ne continuez pas tant que Health n’est pas verte.
Étape 3 — Cliquez sur ⚒ App settings dans la barre latérale. Vous
arrivez sur l’onglet API keys & runtime.
- Collez
ANTHROPIC_API_KEY(préféré — meilleure notation des textes longs) et/ouGEMINI_API_KEY. Obtenez les clés sur https://console.anthropic.com/settings/keys ou https://aistudio.google.com/apikey. - Cliquez sur 💾 Save. Puis cliquez sur ▶ Test Anthropic (ou Gemini) — un aller-retour minimal confirme que la clé fonctionne.
Étape 4 — Passez à l’onglet Profile sur la même page. C’est
l’éditeur YAML direct de config/profile.yml. Modifiez au minimum :
candidate.full_name— remplacez tout texte indicatif (« Jane Smith ») par votre vrai nomcandidate.email,linkedin,github— utilisés dans les lettres de motivationtarget.roles— les intitulés de poste auxquels vous postulereztarget.comp_total_min_usd— rémunération totale minimale ; les offres en dessous sont signalées en section D de chaque évaluationtarget.archetypes— les schémas de carrière que vous acceptez (le champ le plus impactant)
Cliquez sur 💾 Save. Le serveur valide le YAML et appose l’en-tête
canonique # Career-Ops Profile Configuration.
B. CV (à faire une fois, ~10 minutes)
Étape 5 — Cliquez sur ✎ CV dans la barre latérale. Deux colonnes :
l’éditeur à gauche, l’aperçu en direct à droite.
Étape 6 — Choisissez une voie pour remplir l’éditeur :
- Importer un CV existant — cliquez sur 📁 Upload CV, choisissez
l’un de
.docx / .doc / .odt / .rtf / .pdf / .html / .txt / .md. Le serveur convertit en markdown via pandoc ou pdftotext, neutralise le XSS, et dépose le résultat dans l’éditeur. Vérifiez la conversion — les PDF surtout peuvent perdre en fidélité de mise en page. - Coller du markdown directement — la zone de texte est un éditeur markdown ; le volet de droite est ce que le LLM (et votre futur recruteur) verra.
- Conseils de ton : une puce = une réalisation avec une métrique. Restez sous 1500 mots. Sections dans cet ordre : Résumé, Expérience, Projets, Formation, Compétences.
Étape 7 — Cliquez sur 💾 Save (en haut à droite de la page CV). Le
serveur neutralise (<script> / javascript: / gestionnaires inline
supprimés) et écrit cv.md. Toast : « Saved ».
Étape 8 (facultatif) — Cliquez sur 📄 Generate PDF. Lance
generate-pdf.mjs dans le parent (Playwright requis) et le nouveau PDF
se télécharge automatiquement dans votre navigateur une fois terminé. La
liste en bas de page conserve tous les fichiers générés précédemment.
C. Find vacancies (~2 minutes par scan)
Étape 9 — Cliquez sur 🌐 Scan dans la barre latérale. Vérifiez que
portals.yml liste les sites qui vous intéressent (section 5 de cette
aide). Appuyez sur le bouton 🌐 Scan now. Un journal SSE en direct
défile pendant que le scanner parcourt Greenhouse / Ashby / Lever /
Workable / SmartRecruiters / Workday (sites anglophones) et hh.ru / Habr
Career (sites russes si activés).
Étape 10 — Quand le scan se termine, examinez les résultats. Cliquez sur un tag d’entreprise pour filtrer ; cliquez sur l’icône ↗ pour ouvrir la page carrières de l’entreprise dans un nouvel onglet. Chaque offre qui a survécu au filtre de titre est mise en file dans le Pipeline.
D. Score the offers (~30 secondes par offre)
Étape 11 — Cliquez sur Pipeline dans la barre latérale. Vous voyez
chaque URL mise en file par le scanner. Cliquez sur une entrée pour
prévisualiser l’offre en ligne.
Étape 12 — Cliquez sur ▶ Evaluate à côté d’une offre. Cela vous
amène à #/evaluate. Avec une clé API définie, l’évaluation est en direct ;
sans clé, vous obtenez un prompt manuel à coller dans votre propre LLM. Le
mode live produit un score 0–5 par rapport à votre CV sur les sections
A–G (Rôle / Entreprise / Rémunération / Risque / Dépassement / Adéquation
culturelle / Verdict). L’enregistrement va dans reports/<date>-<slug>.md.
Étape 13 — Cliquez sur Reports dans la barre latérale et examinez la
dernière évaluation. Tout ce qui est sous votre comp_total_min_usd est
signalé en rouge en section D. Tout ce qui porte Verdict: pursue est
votre liste restreinte.
E. Decide & deeply research the shortlisted company (~3 minutes)
Étape 14 — Choisissez une offre qui mérite d’être poursuivie. Cliquez
sur Deep research dans la barre latérale. Saisissez le nom de
l’entreprise et le poste. Le modèle produit un dossier d’entreprise en 7
sections (mission, actualités récentes, stack technique, signaux
d’embauche, repères de rémunération, risques, angle recommandé).
L’enregistrement va dans interview-prep/<company>-<role>.md.
F. Apply (~5 minutes par candidature)
Étape 15 — Cliquez sur Apply checklist dans la barre latérale. Collez
l’URL de l’offre + l’offre. L’assistant génère une checklist de soumission
pas à pas :
- Brouillon de lettre de motivation sur mesure (utilise vos
cv.md+profile.yml) - Mots-clés précis à reprendre de l’offre
- Fichiers à joindre (CV PDF — voir étape 8)
- Où postuler (l’URL carrières canonique, pas les redirections d’agrégateurs)
- Rappel : NE JAMAIS soumettre automatiquement — la revue finale et la soumission sont toujours manuelles.
Étape 16 — Ouvrez la page carrières dans un nouvel onglet. Utilisez la checklist de candidature comme liste de tâches. Soumettez via le vrai formulaire de l’entreprise. Joignez le PDF généré à l’étape 8.
Étape 17 — Contactez un véritable humain. Ouvrez le mode Outreach
(#/contacto dans la barre latérale). Le modèle rédige un court message
LinkedIn / e-mail adapté au dossier d’entreprise de l’étape 14.
Personnalisez l’accroche (un détail précis de votre dossier de recherche).
Envoyez-le.
G. Track & follow up (en continu)
Étape 18 — Cliquez sur Tracker dans la barre latérale et ajoutez une
ligne pour la candidature : entreprise, poste, score, statut Applied,
lien vers le rapport, lien vers le dossier de recherche. La date est
remplie automatiquement.
Étape 19 — Une semaine plus tard : ouvrez le mode Follow-up
(#/followup). Rédige un e-mail de relance poli faisant référence à la
candidature initiale. Envoyez. Mettez le statut du tracker à Followed up.
Étape 20 — Quand vous recevez une invitation à un entretien, lancez le
mode Interview prep (#/interview-prep). Génère une préparation ciblée
pour l’entreprise + l’étape précises (system design / comportemental /
coding). S’appuie automatiquement sur le dossier de recherche.
Étape 21 — Offre obtenue ? Mettez le statut du Tracker à Offer et
revoyez la section rémunération de votre rapport d’évaluation — votre
montant minimal d’acceptation est juste là.
TL;DR — l’ordre de la barre latérale suit le flux de travail
Health → App settings → Profile → CV → Scan → Pipeline → Evaluate → Reports → Deep research → Apply checklist → Outreach → Tracker → Follow-up → Interview prep → Activity log
Voilà. 21 étapes, bouton par bouton, de zéro à l’offre.
One-click Auto-pipeline (#/auto) — le raccourci des 21 étapes
Si vous voulez juste noter rapidement une offre précise, sautez le parcours manuel. Barre latérale → ✨ Auto-pipeline (ou le bouton ✨ du Tableau de bord) ouvre un écran dédié : collez l’URL de l’offre, appuyez sur Entrée ou cliquez sur ▶ Run full pipeline, et le serveur exécute toute la chaîne en une passe observable :
- Validating URL — vérification anti-SSRF (
isValidJobUrl) ; rejette loopback /file:/ IP privées / caractères de script. - Fetching job description —
safeGet(DNS épinglé, redirections revalidées) récupère + neutralise l’offre. - Evaluating against your CV — Anthropic (préféré) → repli Gemini → prompt manuel si pas de clé.
- Saving report — écrit
reports/<slug>.mdavec score + légitimité dans l’en-tête. - Adding to tracker — ajoute une ligne à
data/applications.md.
Le retour en direct est un stepper vertical (chaque étape s’allume
running → done / failed). C’est une liste ordonnée avec aria-current sur
l’étape active et une région live polie pour lecteur d’écran annonçant
chaque transition. En cas de succès, la carte de résultat renvoie
directement au rapport enregistré (View report · N/5) et au
tracker. Une étape échouée est marquée en rouge avec son message et le
bouton se réactive pour corriger l’URL et réessayer sans recharger.
Pas de clé API ? Le pipeline s’exécute en mode manuel : les étapes 3 à 5 se replient et vous obtenez une carte de prompt prête à coller (copier dans Claude Code / Anthropic / Gemini). Aucun appel LLM en direct, aucune dépense.
#/auto est partageable : #/auto?url=<encoded>&go=1 ouvre l’écran et
démarre automatiquement. Le bouton ✨ du tableau de bord et cette entrée de
barre latérale arrivent tous deux ici (flux unique cohérent — la modale
transitoire d’avant 1.34 a été promue en cette page).
CLI (v1.38.0). Une commande fait la chaîne :
career-ops-ui setup(bootstrap → install → start). Verbes autonomes :career-ops-ui doctor(vérification env/clés/outils — même moteur que la page Health ; exit 1 sur tout échec requis),career-ops-ui run,career-ops-ui init(assistant fournisseur+clé, v1.39.0). Providers (v1.39.0). L’onglet clés-API ajoute un sélecteurLLM_PROVIDER(auto= défaut Anthropic→Gemini ·claude·gemini) et un champOPENAI_API_KEY(côté CLI Codex/OpenCode).career-ops-ui initest un assistant interactif équivalent.Providers (v1.57.0). L’évaluation live headless couvre désormais Anthropic → Gemini → OpenAI → Qwen → OpenRouter (l’ordre
auto;LLM_PROVIDERen épingle un). OpenRouter — uneOPENROUTER_API_KEYdonne accès à 300+ modèles ; le menuOPENROUTER_MODELcharge le catalogue live d’OpenRouter (proxy côté serveur, repli hors-ligne curé). Corrigé aussi : les clés collées avec un retour à la ligne / des espaces autour sont désormais nettoyées avant validation, donc/#/confign’affiche plus « validation failed » pour aucun fournisseur.
2. App settings & API keys (#/config)
Nouveau en v1.55 → v1.56. Sans aucune clé LLM, une bannière rouge sur chaque écran explique que ⚡ Run-live est en mode prompt-manuel et renvoie ici ; une fois une clé définie, elle devient une puce discrète nommant le fournisseur actif. Avant tout bouton ⚡ Run-live (
#/auto,#/evaluate,#/deep, modes), une estimation honnête du coût s’affiche (p. ex. « Estimated cost: OpenAI gpt-5-codex · ~$0.04/eval », ou une note « pas de coût API » en mode manuel).#/scanrange les filtres secondaires derrière un volet Advanced filters ;#/trackerajoute des puces d’entonnoir cliquables + une pagination côté serveur optionnelle ;#/pipelinevirtualise au-delà de 1000 lignes.
Outils CLI d’IA. L’onglet Outils CLI d’IA indique quels CLI d’agent (Claude Code, Codex, Gemini, OpenCode, Copilot, Qwen, Antigravity) sont installés sur le serveur — une analyse du PATH en lecture seule, sans les exécuter. Apparence → Afficher les logos d’entreprise (désactivé par défaut) affiche le favicon de chaque entreprise dans le tableau de scan, récupéré depuis son propre domaine (jamais un service tiers).
Trois onglets :
- API keys & runtime — formulaire structuré sur le
.envdu projet parent (le même fichier que lisent les scripts Node career-ops au démarrage). Groupé : API keys / Runtime / Regional sources. L’onglet expose aussi des sélecteurs de modèle par fournisseur —OPENAI_MODEL(OpenAI/Codex) aux côtés deANTHROPIC_MODELetGEMINI_MODEL. - Profile — formulaire champ par champ sur
config/profile.yml(web-ui 1.32.0). L’enregistrement fusionne dans le fichier — vos archétypes, proof points et toute clé personnalisée sont préservés intacts. - Modes — formulaire structuré pour
modes/_profile.md(web-ui 1.54.3), dérivé du schéma documenté. Les sections de type liste — Target Roles / Adaptive Framing / Comp Targets — s’affichent comme des champs répétables (ajout/suppression de lignes) ; les sections en prose — Exit Narrative / Location Policy — s’affichent comme des textareas étiquetées ; toute section inconnue ou non-liste retombe sur une textarea verbatim étiquetée. L’enregistrement fusionne par section — le préambule, les sections intactes et les sections personnalisées sont préservés à l’octet près. Un volet Advanced: raw markdown reste disponible pour les éditions plein fichier — ajout/suppression de sections ou édition du préambule.
Un enregistrement dans n’importe quel onglet se propage immédiatement — pas de redémarrage du serveur.
Configurer votre fournisseur LLM (pas à pas). L’évaluation live ⚡ de l’UI web tourne headless et utilise une seule clé API. Elle fonctionne par « OU » — définissez n’importe laquelle et ça marche ; avec plusieurs, auto les préfère dans cet ordre : Anthropic → Gemini → OpenAI → Qwen. (career-ops lui-même est agnostique du CLI — vous l’exécutez aussi dans Claude Code, Codex, Gemini, OpenCode, Qwen, Copilot ou Kimi ; c’est distinct de cette clé headless.)
- Ouvrez
#/config→ l’onglet API keys & runtime. - Choisissez votre fournisseur dans
LLM_PROVIDER:auto(utilise la clé définie), ou forcez-en un avecclaude/gemini/openai/qwen. - Renseignez la clé + le modèle du fournisseur choisi :
- Anthropic — définissez
ANTHROPIC_API_KEY(console.anthropic.com), éventuellementANTHROPIC_MODEL(défautclaude-sonnet-4-6). - Gemini — définissez
GEMINI_API_KEY(aistudio.google.com/apikey), éventuellementGEMINI_MODEL(défautgemini-2.0-flash). - OpenAI — définissez
OPENAI_API_KEY(platform.openai.com), éventuellementOPENAI_MODEL(défautgpt-5-codex). - Qwen — définissez
QWEN_API_KEY(Alibaba Model Studio / DashScope, dashscope.console.aliyun.com), éventuellementQWEN_MODEL(défautqwen-max). Pour l’endpoint Chine continentale, définissezQWEN_BASE_URLdans le.envbrut.
- Anthropic — définissez
- Cliquez sur Save. Les clés s’écrivent dans le
.envdu projet parent ; le changement prend effet immédiatement — pas de redémarrage. - Vérifiez sur
#/evaluate: collez une URL/description d’offre et appuyez sur ⚡ Run live. L’en-tête du résultat indique quel fournisseur a tourné (anthropic/gemini/openai/qwen). Aucune clé définie nulle part → vous obtenez le prompt manuel à copier-coller.
Les secrets sont masqués après enregistrement et jamais journalisés. Les champs d’identifiant de modèle (*_MODEL) ne sont pas secrets.
Profile tab (field form — v1.32.0)
Avant la v1.32.0, cet onglet était une unique textarea YAML brute où chaque réglage vivait dans un bloc indifférencié. C’est désormais un formulaire structuré, champs groupés en trois sections repliables :
- Candidate — Nom complet (requis), E-mail, Téléphone, Localisation, LinkedIn, GitHub, URL de portfolio, X / Twitter.
- Narrative — Accroche, Récit de départ.
- Compensation — Fourchette cible, Devise, Minimum de rupture, Flexibilité de localisation.
- Éditeurs de tableaux structurés (web-ui 1.35.0) — éditeurs ajout/suppression de lignes pour les champs de type liste, pour que même ceux-ci n’aient plus besoin du YAML brut : Target roles + Superpowers (listes de chaînes) ; Archetypes (lignes name / level / fit) ; Proof points (lignes name / url / hero-metric). Les lignes vides sont supprimées ; une liste vidée retire proprement la clé. Même garantie fusion-pas-remplacement — chaque tableau que vous ne touchez pas survit intact.
Pourquoi l’enregistrement est sûr :
- Le formulaire n’envoie que les 14 chemins scalaires modélisés sous forme
{ fields: { "candidate.full_name": … } }. Le serveur lit leconfig/profile.ymlexistant, définit/efface seulement ces feuilles, et re-sérialise tout l’objet — donc les tableaux imbriqués que le formulaire ne modélise pas (target_roles.archetypes,narrative.proof_points,narrative.superpowers) et toute clé personnalisée ajoutée à la main survivent à l’aller-retour intacts. Vider un champ retire proprement cette clé (pas de résiduphone: ""). - La validation exige toujours un nom complet ; l’en-tête
# Career-Ops Profile Configurationest apposé automatiquement. - Un compromis : un enregistrement par formulaire re-sérialise le YAML,
donc les commentaires inline
#sont perdus. Pour préserver les commentaires ou éditer des tableaux imbriqués, utilisez le volet Advanced: edit raw YAML en bas de l’onglet — c’est l’éditeur plein fichier d’avant la 1.32, inchangé (remplace tout le fichier à l’enregistrement). - Le résumé en lecture seule de
#/profileest le compagnon visuel.
Recognized keys
| Key | What it does | Where to get it |
|---|---|---|
ANTHROPIC_API_KEY |
Active les appels live au SDK Anthropic. Préféré quand Anthropic + Gemini sont tous deux définis — meilleure sortie structurée longue pour la notation d’offres et la recherche approfondie. | https://console.anthropic.com/settings/keys |
ANTHROPIC_MODEL |
Remplace le défaut claude-sonnet-4-6. Essayez claude-opus-4-7 pour un raisonnement plus dur, claude-haiku-4-5-20251001 pour rapide-et-pas-cher. |
— |
GEMINI_API_KEY |
Repli quand pas de clé Anthropic. Utilisé par gemini-eval.mjs pour le mode oferta. Le palier gratuit suffit à faible volume. |
https://aistudio.google.com/apikey |
GEMINI_MODEL |
Remplace le modèle Gemini par défaut. | — |
(server uses default UA) |
Requis pour lancer des scans hh.ru hors de Russie (l’API renvoie 403 sur un User-Agent nu). Enregistrez une app sur https://dev.hh.ru/admin et utilisez sa chaîne UA. |
dev.hh.ru |
PORT |
Port d’écoute Express. Défaut 4317. | — |
HOST |
Adresse d’écoute. Défaut 127.0.0.1. Mettre 0.0.0.0 expose l’UI sur le LAN — pas encore de barrière d’authentification, voir le doc Production-readiness. |
— |
Behavior
- Read (
GET /api/config) renvoie chaque clé reconnue. Les clés secrètes (ANTHROPIC_API_KEY,GEMINI_API_KEY) sont masquées — vous voyezsk-ant•••••••a1b2, jamais la valeur complète. - Save (
POST /api/config) valide chaque valeur, écrit dans<parent>/.env, et applique immédiatement au processus en cours. Pas de redémarrage. - Une valeur vide supprime la clé. Utile pour cesser d’utiliser une IP / un VPN russe.
Smoke-test buttons
Après l’enregistrement, cliquez sur ▶ Test Anthropic ou ▶ Test Gemini — les deux envoient un prompt minuscule (≤256 tokens en sortie) donc vous ne dépensez quasiment rien tout en confirmant que la clé est bien câblée. Renvoie un échantillon d’~200 caractères en cas de succès.
3. Profile (#/profile — also reachable as #/settings)
Une vue résumé en lecture seule de config/profile.yml. Pour éditer,
allez dans App settings → onglet Profile (#/config → Profile) — depuis
web-ui 1.32.0 c’est un formulaire champ par champ (Candidate / Narrative /
Compensation), pas un bloc YAML brut. Les enregistrements fusionnent dans le
même fichier ; cette page se re-parse au rechargement.
Les champs qui comptent le plus :
candidate.full_name— utilisé dans chaque prompt. Remplacez le modèleJane Smithavant de scanner quoi que ce soit pour de vrai, sinon vos lettres de motivation générées partiront sous le nom indicatif.candidate.email,linkedin,github— référencés dans la génération de lettre de motivation et la checklist de candidature.target.roles— intitulés de poste acceptés. Le filtre positif du scanner l’utilise implicitement (viaportals.yml::title_filter).target.comp_total_min_usd— rémunération totale minimale. La section D de chaque évaluation signale les offres en dessous.target.archetypes— le champ le plus important. Ce sont les schémas de carrière que vous acceptez (p. ex.Tech-Lead-Backend,Founding-Engineer,Data-Platform). Chaque offre est comparée à eux et le meilleur archétype atterrit dans l’en-tête du rapport.
La page Health expose une vérification Profile customized qui échoue
tant que full_name correspond à un nom indicatif connu.
4. CV (#/cv)
Source unique de vérité pour chaque évaluation, recherche approfondie et
lettre de motivation. Vit dans cv.md à la racine du projet parent.
Editing options
- Le coller directement — la textarea de gauche est un éditeur markdown. Le volet de droite reflète ce que voit le LLM (et votre futur recruteur).
- 📁 Upload CV — choisissez un fichier local dans l’un de ces formats et
le serveur le convertit en markdown pour vous :
- Formats texte —
.md,.markdown,.txt,.html,.htmsont passés tels quels (le HTML passe par pandoc → markdown GFM). - Formats Office —
.docx,.doc,.odt,.rtfsont convertis via pandoc (brew install pandocsur macOS,apt install pandocsur Linux). - PDF —
.pdfest extrait via pdftotext de Poppler (brew install poppler/apt install poppler-utils). - Le markdown converti atterrit dans l’éditeur ; cliquez sur 💾 Save pour persister. Le résultat est neutralisé (même nettoyage XSS que le collage).
- Plafond strict : 10 Mo par import. Fichiers plus gros → 413.
- Formats texte —
- Depuis LinkedIn — voie la plus simple : ouvrez Claude Code dans le
projet parent, lancez
/career-ops, collez l’URL de votre LinkedIn, et demandezextract my CV from this and write it to cv.md.
What gets sanitized
Côté serveur, chaque PUT vers /api/cv passe par stripDangerousMarkdown :
- balises
<script>,<iframe>,<object>,<embed>,<svg>,<style>,<form>— entièrement retirées. - Gestionnaires d’événements inline (
onclick=,onerror=, etc.) — supprimés. - Schémas d’URI
javascript:,vbscript:,data:text/html— neutralisés.
La réponse inclut sanitized: true dès que l’un des éléments ci-dessus a
été retiré, pour que vous sachiez si la source contenait quelque chose de
malveillant.
Taille max du corps : 1 Mo. Au-delà → 413.
Other buttons
- sync-check — lance
cv-sync-check.mjsdans le projet parent. Signale les incohérences : un projet listé dans votre CV mais pas dans les archétypes dedata/applications.md, etc. - 📄 Generate PDF — diffuse
generate-pdf.mjs. La sortie atterrit dansoutput/*.pdf. Nécessite Playwright (la page Health indique s’il est installé dans lenode_modulesdu parent). Une fois la génération terminée, le PDF le plus récent est téléchargé automatiquement dans votre dossier Téléchargements par défaut ; la liste de la page conserve tous les fichiers générés précédemment.
Tone / format tips
- Une puce = une réalisation avec une métrique. « Réduit la latence p99 de 38 % » bat « amélioré les performances » pour toute grille d’évaluation.
- Sections dans cet ordre : Résumé (3–5 lignes), Expérience (anti-chronologique), Projets (max 5), Formation, Compétences (dédoublonnées, pas de soupe de mots-clés).
- Restez sous 1500 mots. La grille de notation utilise une info dense ; un CV tentaculaire est pénalisé pour le bruit.
5. Portals & sources (portals.yml)
La config du scanner vit dans portals.yml à la racine parente. Trois
sections comptent. Les trois sections de la SPA (ci-dessous) correspondent
1:1 au schéma canonique career-ops.org de
scan-job-portals.
Raccourci : l’URL
#/portalsse résout désormais directement vers App settings et (quand une source régionale est configurée) saute au groupe Regional sources — donc un lien#/portalsmis en favori ou tapé ne renvoie plus de 404 (v1.42.0).
title_filter
title_filter:
positive: [backend, engineer, senior, tech lead, golang, php]
negative: [junior, intern, frontend, ios, android, java]
seniority_boost: [Senior, Staff, Lead, Principal]
Une offre scannée passe quand son titre contient au moins un mot-clé positif ET aucun mot-clé négatif. Réglez les deux. Les mots-clés sont des sous-chaînes insensibles à la casse.
seniority_boost est la troisième clé du title-filter. Les mots-clés listés
ici ne filtrent rien — ils poussent les offres correspondantes plus haut
dans les résultats, pour qu’un « Senior Backend Engineer » passe au-dessus
d’un « Engineer ». Défaut : ["Senior", "Staff", "Lead"]. Réglez selon
l’intitulé de vos postes cibles.
Commencez avec 3–5 mots-clés positifs pour la clarté ; élargissez plus tard.
location_filter (optional — web-ui 1.33.0, parent #570)
location_filter:
allow:
- "Remote"
- "United States"
- "Atlanta"
block:
- "India"
- "London"
- "Germany"
Filtre les offres scannées par leur chaîne de localisation (sous-chaîne
insensible à la casse), appliqué par la passe ATS et la passe régionale.
Sémantique, identique au scan.mjs canonique de career-ops :
- Pas de clé
location_filter→ toute localisation passe (défaut). - Une offre avec une localisation vide/manquante → passe (une donnée manquante n’est pas pénalisée).
- Une correspondance de mot-clé
block→ rejetée (block prime sur allow). allowvide → passe (block l’a déjà filtrée).allownon vide → doit correspondre à au moins un mot-clé.
Clé de premier niveau dans portals.yml (sœur de title_filter, non
imbriquée sous russian_portals). Utilisez-la pour écarter les offres qui
ont survécu au title-filter mais sont dans une région que vous ne pouvez
pas prendre.
Commencez avec 3–5 mots-clés positifs pour la clarté ; élargissez plus tard.
content_filter (optionnel — web-ui 1.75.0, parent #974). Une clé de premier niveau sœur de location_filter, avec les mêmes listes de mots-clés positive / negative, mais comparée au texte de la description / de l’extrait d’une offre plutôt qu’à sa localisation :
content_filter:
positive: ["python", "machine learning"]
negative: ["security clearance", "on-site only"]
Sémantique identique à location_filter : pas de clé → tout passe ; une offre avec une description vide/manquante passe (une donnée manquante n’est pas pénalisée) ; une correspondance negative → rejetée ; positive vide → passe ; positive non vide → doit correspondre à au moins un mot-clé (sous-chaîne insensible à la casse). Appliqué à la fois par la passe ATS et par les passes régionales. Seules les sources qui livrent une description/un extrait (p. ex. RSS) sont concernées — toute autre offre passe — donc l’activer ne supprime jamais silencieusement des lignes des sources qui ne portent pas de corps. Utilisez-la pour écarter une offre qui a survécu au title-filter mais dont le corps révèle un facteur rédhibitoire.
search_queries
search_queries:
- name: "Greenhouse — Rails Engineer"
query: 'site:job-boards.greenhouse.io "Rails Engineer" OR "Ruby on Rails" remote'
enabled: true
- name: "Ashby — Senior Backend"
query: 'site:jobs.ashbyhq.com "Senior Backend" remote'
enabled: false
search_queries pilote le scan IA Option B (/career-ops scan dans Claude
Code / Codex). Elles ne sont PAS exécutées par le npm run scan in-process
(qui ne touche que les API publiques des sites). Utilisez-les pour découvrir
des postes dans des entreprises pas encore dans tracked_companies. Mettez
enabled: false pour garder une entrée sans l’exécuter.
tracked_companies
tracked_companies:
- { name: Stripe, enabled: true, careers_url: https://job-boards.greenhouse.io/stripe }
- { name: Linear, enabled: true, careers_url: https://jobs.ashbyhq.com/linear }
- { name: JetBrains, enabled: true, careers_url: https://jobs.lever.co/jetbrains }
Champs requis par entrée : name et careers_url. Optionnels : api
(endpoint Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday
explicite), enabled: true|false pour inclure/exclure sans supprimer
l’entrée. Le scanner ATS détecte l’ATS depuis le motif d’URL
(job-boards.greenhouse.io/<slug> → Greenhouse, etc.) et récupère la
boards-api publique de chaque entreprise directement. Les entreprises sans
ATS reconnaissable sont ignorées (la carte Active Companies sur /#/scan
les affiche en gris avec ○).
rss (RSS / Atom boards)
tracked_companies:
- { name: LaraJobs, enabled: true, provider: rss, rss: https://larajobs.com/feed }
- { name: WeWorkRemotely, enabled: true, provider: rss, rss: https://weworkremotely.com/remote-jobs.rss }
Pointez le scanner vers n’importe quel site d’emploi publiant un flux RSS/Atom (LaraJobs, WeWorkRemotely, RemoteOK, golangprojects, …) en ajoutant une entrée avec provider: rss et une clé rss: (ou feed_url:) — sans modification de code. L’adaptateur RSS analyse chaque <item> (CDATA + entités HTML, titres/entreprises nettoyés des balises), le normalise en offre, et applique le même flux title_filter / location_filter + déduplication + ajout au pipeline que les sources ATS. RSS apparaît ensuite comme source sélectionnable dans le menu de filtre de #/scan. (web-ui v1.62.x)
russian_portals
russian_portals:
sources: ["hh", "habr", "trudvsem", "getmatch", "geekjob"] # or just one
area: 113 # 1=Moscow, 2=SPb, 113=Russia, 1001=remote
per_page: 50
only_remote: false
queries:
- "Senior PHP"
- "Senior Go"
- "Тимлид PHP"
queries sont des correspondances de sous-chaînes insensibles à la casse
sur les titres d’offres de hh.ru et Habr Career. Attention au
chevauchement avec la liste négative — si "Senior PHP" est dans
queries mais "php" finit dans title_filter.negative, le scan ne
renverra aucun résultat et la console vous avertira du conflit.
Configuring Russian portals — detailed setup guide
La v1.29.0 livre 5 adaptateurs russophones. Deux n’ont besoin de rien de
plus que l’UA par défaut (habr-career, scrape HTML ; trudvsem, API
open-data gouvernementale — pas de clé, pas de barrière IP). Deux sont des
scrapes HTML de sites tech (getmatch, geekjob — sans clé non plus). Un
est l’API canonique hh.ru qui peut renvoyer 403 depuis des IP non russes
sauf si vous définissez une variable HH_USER_AGENT via App settings →
API keys & runtime (ou lancez le serveur depuis une IP / un nœud de sortie
VPN russe).
Source inventory
| Source key | Display label | Type | Auth | Geo restriction |
|---|---|---|---|---|
hh |
hh.ru | JSON API | optional HH_USER_AGENT |
non-RU IPs may 403 |
habr |
Habr Career | HTML | none | none |
trudvsem |
Trudvsem | JSON API (open-data) | none | none |
getmatch |
GetMatch | HTML | none | none |
geekjob |
GeekJob | HTML | none | none |
Step 1 — Open portals.yml
Le fichier vit dans la racine parente career-ops/ (PAS dans web-ui/).
S’il n’existe pas encore, copiez l’exemple livré avec le projet parent :
# from the parent career-ops/ root (NOT web-ui/)
cp templates/portals.example.yml portals.yml
$EDITOR portals.yml
Step 2 — Enable all 5 sources
Ajoutez ou mettez à jour le bloc russian_portals pour lister chaque source
à scanner. L’ordre dans le tableau est sans importance ; le scanner les
parcourt dans l’ordre du registre.
russian_portals:
sources: ["hh", "habr", "trudvsem", "getmatch", "geekjob"]
area: 113 # 1=Moscow, 2=SPb, 113=Russia, 1001=remote
per_page: 50 # how many vacancies per query per source
only_remote: false # set true to keep only remote postings
queries:
- "Senior PHP"
- "Senior Go"
- "Backend Senior"
- "Тимлид PHP"
Step 3 — Tune queries and filters
queries sont les chaînes que le scanner utilise pour chercher sur chaque
source. Chaque requête s’exécute une fois sur chaque source — donc 4
requêtes × 5 sources = 20 appels par scan. Gardez la liste ciblée (3–7
requêtes) pour garder le temps de scan sous une minute. area est le code
de région hh.ru (les autres sources l’ignorent). per_page plafonne le
nombre d’offres renvoyées par source et par requête. only_remote: true
filtre chaque résultat en remote-only au niveau de l’adaptateur (le tableau
de résultats garde une puce Remote distincte).
Common pitfalls
Collision avec la liste négative. Si un mot d’une requête ("php",
"senior") apparaît aussi dans title_filter.negative, chaque résultat est
filtré avant que vous le voyiez. Le scanner émet un avertissement de
collision sur stderr au moment du scan — cherchez la ligne ⚠ config: query "Senior PHP" contains "php" which is in the negative list. Corrigez en
retirant le mot en conflit de negative :
title_filter:
positive: [backend, senior, lead, php, go, golang, python]
negative: [junior, intern, frontend, ios, android]
russian_portals:
queries:
- "Senior PHP" # OK — "php" no longer in negative list
- "Senior Go"
Disabling one source temporarily
Pour désactiver une source sans supprimer ses données, retirez simplement sa
clé de sources :
russian_portals:
sources: ["hh", "habr", "trudvsem"] # only 3 of 5 sources will run
Verifying the setup
Après avoir enregistré portals.yml :
# 1. Save portals.yml.
# 2. In the SPA, switch to #/scan.
# 3. Click 🌐 Scan now.
# 4. Watch the SSE log for the per-source line per query:
# "Senior PHP"
# hh.ru 18
# habr 21
# trudvsem 3
# getmatch 0
# geekjob 2
# A value of 0 is normal for some queries — it just means that
# source had no matches. A "geo-blocked" or "timeout" line means
# the adapter reached the site but couldn't read results.
CLI bootstrap flow (scan-job-portals)
Le setup canonique career-ops (à lancer une fois depuis la racine parente) :
cp templates/portals.example.yml portals.yml
$EDITOR portals.yml
C’est tout le bootstrap. Éditez les trois sections (title_filter,
tracked_companies, search_queries, optionnellement russian_portals),
enregistrez, et vous êtes prêt à scanner.
SPA bootstrap behavior
Au premier lancement, le serveur ajoute un bloc russian_portals: documenté
à portals.yml s’il manque — idempotent (le second démarrage est sans effet
car la ligne littérale russian_portals: est désormais là). Les sections
anglaises ne sont PAS auto-injectées ; elles viennent du
templates/portals.example.yml que vous avez copié selon le bootstrap
canonique ci-dessus.
6. Health (#/health)
Chaque barrière de configuration, en badges OK / OPTIONAL / FAIL. Lisez ceci avant d’ouvrir tout ticket « ça ne marche pas ».
Utilisation et coût de l’IA. La page Utilisation IA (💳, à côté de Santé) montre les tokens des générations IA en direct par fournisseur sur 24 h / 7 j / 30 j / tout, avec un coût estimé en USD issu d’une table de prix modifiable (jamais facturé). Un compteur UTILISATION compact est aussi épinglé en bas de la barre latérale gauche sur chaque page — les mêmes totaux de jetons 24h/7j/30j et un coût estimé sur 24 heures, actualisé en direct ; le menu reste toujours dégagé au-dessus, et cliquer sur son en-tête le replie.
Required checks (system can’t function without these)
Node version≥ 18 — le serveur utilise lesfetchetnode:testnatifs.Project root— queCAREER_OPS_ROOT(env ou auto-détecté) existe.cv.md,config/profile.yml,portals.yml,data/applications.md,data/pipeline.md,modes/oferta.md.
Optional checks (warnings only)
Profile customized—candidate.full_namen’est pas le nom indicatif du modèle.GEMINI_API_KEY/ANTHROPIC_API_KEY— définies dans.env.(server uses default UA)— ne compte que si vous scannez hh.ru hors de Russie.Playwright (parent node_modules)— requis pour la génération PDF etcheck-liveness.mjs. Installez aveccd $CAREER_OPS_ROOT && npm install && npx playwright install chromium.Parent project dependencies—cd $CAREER_OPS_ROOT && npm installsi manquant.- répertoires
data/,reports/,output/,jds/— créés automatiquement à la première écriture.
Quand le serveur est exposé au-delà de loopback (HOST=0.0.0.0), les chemins
absolus et la version Node exacte sont remplacés par "hidden" dans la
réponse pour qu’un voisin curieux ne puisse pas identifier votre
installation.
Run buttons
- ▶ Doctor lance
node doctor.mjset affiche la sortie dans une modale. - ▶ Verify pipeline lance
node verify-pipeline.mjs.
7. Scan (#/scan)
Le scanner parcourt chaque site activé, déduplique par rapport à votre
historique, et écrit les trouvailles dans data/last-scan.json et
data/pipeline.md.
Rechercher + Exclure. La boîte Rechercher traite les virgules comme un OU (« rôles à trouver ») ; le nouveau champ Exclure masque les lignes correspondant à un mot séparé par des virgules. Les deux sont enregistrés avec vos recherches.
One-click scan (SPA)
🌐 Scan exécute chaque source activée en une seule passe :
- Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday (la passe
ATS) pour chaque entreprise de
tracked_companiesavec une URL ATS reconnaissable. - Les agrégateurs de la v1.75.0 pour chaque entrée de
tracked_companiesqui en active un : RemoteOK / Remotive / Working Nomads (flux remote couvrant tout le board,provider: <slug>) et IBM / Arbeitsagentur / Glints / Jobstreet · SEEK (pilotés par config, bloc<provider>:par entrée). - API hh.ru + Habr Career + Trudvsem + GetMatch + GeekJob pour chaque
requête de
russian_portals.
Deux phases, un clic (v1.29.2). Le bouton 🌐 Scan unique pilote À LA FOIS la passe ATS et la passe régionale dans un seul flux SSE. Vous verrez deux en-têtes de phase dans le journal, dans l’ordre :
▶ ATS scan (Greenhouse + Ashby + Lever)— sites ATS EN.▶ Regional scan (hh.ru + Habr Career)— 5 sources RU du registre.
Chaque phase se termine par un résumé ✓ done · NEW=N. Si vous ne voyez que
la phase ATS, votre instance est sur un build d’avant la v1.29.2 — mettez à
jour. Avant la v1.29.2, le client SSE se fermait au premier événement done
et la phase régionale était silencieusement abandonnée
(tests/scan-stream-multi-phase.test.mjs est le filet de régression).
Le journal SSE en direct défile dans le volet de droite pendant le scan.
Cliquez sur Stop (ou naviguez ailleurs) pour interrompre — le serveur
annule les requêtes HTTPS en cours via AbortController.
Filtering results
Sous le journal, le tableau de résultats affiche les lignes de
data/last-scan.json.
v1.78.1 — rafraîchissement automatique en direct. Le tableau de résultats se met désormais à jour automatiquement pendant qu’un scan tourne et une fois de plus juste après la fin — sans rechargement manuel ni changement de page.
v1.80.0 — Max par source & mise en quarantaine des sources. Le champ Max par source à côté du bouton Scan plafonne le nombre d’offres que chaque board contribue (vide/0 = illimité, la valeur par défaut) — pratique quand un board énorme dominerait autrement. Par ailleurs, toute source qui renvoie un 404 / 410 permanent est écrite dans
data/scan-quarantine.jsonet ignorée lors des scans suivants (auto-réparation : réessayée après 14 jours), pour que les slugs morts cessent de polluer le journal. Désactivez avecscan_quarantine: falsedansportals.yml.
Filtres :
- Texte libre — correspondance de sous-chaîne sur le titre / l’entreprise.
- Menu déroulant Source — Arbeitsagentur / Ashby / BambooHR / Breezy HR /
Comeet / GeekJob / Glints / Greenhouse / GetMatch / Habr Career / hh.ru / IBM /
Jobstreet · SEEK / Lever / Personio / Recruitee / RemoteOK / Remotive / RSS /
SmartRecruiters / SolidJobs / Teamtailor / Trudvsem / We Work Remotely / Workable / Workday / Working Nomads
(auto-rempli depuis
GET /api/scan/sources). - Menu déroulant Remote / Hybrid / Onsite.
- Menu déroulant Country (v1.78.0) — un filtre géographique alimenté par les pays détectés dans les résultats actuels, chacun affiché avec son emoji de drapeau et un décompte (p. ex.
🇩🇪 Germany (12)). Choisissez-en un pour ne garder que les postes liés à ce pays. La détection mappe la localisation en texte libre d’une offre (noms de pays/alias + ~100 grandes villes du marché de l’emploi) vers un pays ; elle est prudente et ne devine jamais, de sorte qu’une offre dont la localisation ne peut être résolue — ou une annonce purement « Remote » — reste sous All countries. Combinez-le avec le menu déroulant de type de travail pour trouver des postes liés à un pays et à distance. - Menu déroulant Publié depuis (v1.80.0) — un filtre d’ancienneté côté client (Dernières 24 heures / 7 jours / 30 jours). Les lignes dont la
pubDateest plus ancienne sont masquées ; les lignes sans date indiquée passent (l’absence de donnée n’est pas pénalisée). - ★ Favoris (v1.80.0) — cliquez sur le ☆ d’une ligne pour mettre une offre en favori (stocké dans
localStoragepar URL) ; cochez ★ Favoris dans le panneau de filtres pour n’afficher que les lignes favorites. Les favoris survivent aux scans et aux rechargements. - Recherches enregistrées (v1.80.0) — la barre au-dessus des filtres : nommez le jeu de filtres actuel et 💾 Enregistrer, puis ré-appliquez-le depuis le menu déroulant ou 🗑 Supprimer-le. Stocké dans
localStorage; une valeur corrompue/modifiée se réinitialise proprement à vide. - Puces de stack (PHP / Go / Backend / Senior / …) — auto-détectées par
ligne par
Skills.detectTechetSkills.detectLevel. Intersection multi-sélection — choisirPHP + Seniormontre les lignes qui ont les DEUX. - Puces dynamiques sous les puces de stack statiques — les 25 tokens capitalisés les plus fréquents des titres, pour que l’UI s’adapte aux postes que vous scannez réellement (marketing, design, finance…) au lieu d’être verrouillée au vocabulaire backend-engineer.
Active Companies card
Une carte repliable listant chaque entreprise de portals.yml avec son
statut de scan :
- tag vert ✓ — support API direct (Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday).
- tag gris ○ — repli sur un prompt de recherche web (pas de correspondance API).
Cliquez sur le nom de l’entreprise → remplit le filtre de résultats
ci-dessus avec ce nom. Cliquez sur l’icône ↗ → ouvre le careers_url de
l’entreprise dans un nouvel onglet.
CLI scan flow (scan-job-portals)
Deux façons de scanner côté CLI (les deux déposent les URL dans le même
data/pipeline.md que lit la SPA) :
Option A — script direct (~30 s, zéro token IA) :
npm run scan # all Greenhouse/Ashby/Lever boards
npm run scan -- --dry-run # preview without persisting
npm run scan -- --company Anthropic # narrow to one tracked company
Fonctionne uniquement pour Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday (URL ATS reconnaissables). Aucun token IA consommé — il touche directement les API publiques des sites.
Option B — scan navigateur piloté par l’IA :
/career-ops scan
Dans Claude Code / Codex / Cursor / Gemini CLI. Utilise des tokens de modèle.
Visite chaque page tracked_companies directement et peut découvrir des
sites non-API (pages carrières, ATS personnalisés, portails régionaux). Plus
lent mais plus large. Utile quand une passe ATS ne renvoie rien pour une
cible que vous savez en train de recruter.
Sortie (les deux voies) — nouvelles URL d’offres ajoutées à
data/pipeline.md, chaque URL visitée journalisée dans
data/scan-history.tsv (dédup sur tous les scans futurs), résumé imprimé :
entreprises scannées · offres trouvées · filtrées par titre · doublons
ignorés · nouvelles offres ajoutées.
Seuils d’action par score (à appliquer après que /career-ops pipeline
note en lot les nouvelles URL) :
| Score | Étape suivante recommandée |
|---|---|
| ≥ 4.5 | /career-ops apply — forte adéquation, foncez tout de suite |
| 4.0 – 4.4 | postulez, ou /career-ops contacto pour une intro chaleureuse |
| 3.5 – 3.9 | /career-ops deep — recherchez d’abord |
| < 3.5 | passez sauf raison personnelle précise |
Le #/dashboard et le #/tracker de la SPA mettent en évidence chaque
ligne à 4.0 ou au-dessus pour choisir une action sans rien relancer.
Follow-up commands
Après la notation, les suites canoniques sont :
/career-ops apply— Remplir la candidature avec des réponses sur mesure/career-ops contacto— Rédiger une prise de contact LinkedIn / e-mail/career-ops deep— Rechercher l’entreprise / le poste en profondeur/career-ops tracker— Voir le statut du pipeline
hh.ru — récupéré depuis le site web (IP russe requise depuis juillet 2026)
hh.ru est scanné en lisant son site public de recherche (hh.ru/search/vacancy), comme Habr Career — sans clé ni configuration. Depuis juillet 2026, cependant, hh.ru renvoie HTTP 451 (blocage légal régional) aux IP hors de Russie : le scan ne fonctionne donc que depuis une IP russe — lancez le serveur depuis la Russie ou via un VPN avec sortie russe. Au premier 451 (ou 403 anti-bot), le scanner désactive hh.ru pour le reste de l’exécution et l’indique dans le journal, si bien que les autres sources russes se terminent normalement. L’API JSON (api.hh.ru) n’est volontairement pas utilisée : elle renvoie 403 forbidden à tout client programmatique, quels que soient l’IP ou le User-Agent.
Même depuis un réseau qui semble correct, hh.ru peut considérer l’IP de sortie comme un VPN/proxy (toute IP de datacenter/hébergeur compte) et rediriger le scan en 302 vers un interstitiel /vpncheeck (“VPN мешает работе сайта”) qui renvoie HTTP 200 avec zéro offre. Le scanner détecte cette redirection, désactive hh.ru pour le reste de l’exécution et l’indique dans le log. La solution est côté réseau : assurez-vous que le trafic sort réellement par une IP résidentielle — un VPN ou proxy système reste souvent actif même quand l’interrupteur du navigateur est coupé (vérifiez votre IP de sortie réelle, p. ex. sur api.ipify.org).
8. Pipeline (#/pipeline)
Boîte de réception des URL en attente d’évaluation. Vit dans
data/pipeline.md.
Bande d’aperçu. Une bande compacte en haut montre votre pipeline d’un coup d’œil — combien d’URL en boîte, combien suivies, et les décomptes Applied/Responded/Interview/Offer, chacun renvoyant au tracker.
Adding URLs
Trois façons :
- Tapez / collez une URL dans le champ + cliquez sur + Add.
- Utilisez la recherche globale de la barre supérieure (son badge affiche
Enter) : collez n’importe quel lien
http(s)://…et appuyez sur Enter pour ouvrir l’auto-pipeline ; tapez tout autre texte et Enter saute vers#/scanavec ce terme pré-rempli (v1.78.1). Ctrl/Cmd+K continue de focaliser le champ là où le navigateur le permet. Le logo de marque renvoie au tableau de bord. - Lancez un Scan (voir ci-dessus) — les nouvelles trouvailles vont au pipeline automatiquement.
Chaque URL passe par isValidJobUrl() côté serveur. Loopback (localhost,
127.0.0.1), file://, javascript:, les IP littérales, et les chaînes
avec des caractères de modèle (<, >, ") renvoient toutes 400.
Server-side preview pane
Cliquez sur une ligne du pipeline pour charger un aperçu à droite. La plupart
des sites ATS n’envoient pas d’en-têtes CORS, donc le navigateur ne peut pas
les récupérer directement ; le serveur relaie la requête, retire les balises
<script> / <style> / HTML, et renvoie jusqu’à 8 Ko de texte brut.
Le proxy d’aperçu suit les redirections manuellement avec une validation
SSRF par saut — chaque en-tête Location repasse par isValidJobUrl(),
donc un site hostile ne peut pas vous renvoyer vers loopback / IP privée /
file://. Plafonné à 3 sauts, délai de 15 secondes.
Row actions
- ▶ — saute à
#/evaluate?url=…avec l’URL pré-remplie. - ✕ — retire l’URL de
data/pipeline.md.
Top-right buttons
- ⚡ Evaluate first — ouvre la première URL en file sur la page Evaluate, prête à noter.
- Scan — retour au scanner si vous voulez plus d’URL.
9. Evaluate (#/evaluate)
Note une seule description de poste par rapport à cv.md et
config/profile.yml. Renvoie une évaluation structurée A–G selon
modes/oferta.md plus un score 0–5.
Input
Collez l’offre dans la textarea, ou arrivez ici depuis #/pipeline avec
?url=<href> — la page récupère l’URL via le même proxy anti-SSRF utilisé
pour les aperçus du pipeline et pré-remplit la textarea.
Cliquez sur 💾 Save JD pour persister l’offre dans
jds/jd-<date>-<ts>.txt pour la traçabilité (ou passez save: true dans
l’appel API — même effet).
Fallback chain
- Anthropic — préféré quand
ANTHROPIC_API_KEYest définie. Le serveur regroupecv.md,config/profile.yml,modes/_shared.mdetmodes/oferta.mddans un bloc<project_context>avant le prompt (chaque fichier plafonné à 16 Ko, prompt complet plafonné en douceur à 200 Ko). Renvoie du markdown ancré directement à la page. - Gemini — quand seule
GEMINI_API_KEYest définie. Le serveur lancegemini-eval.mjsavec l’offre en fichier temporaire. Le modèle du palier gratuit (gemini-2.0-flash) convient à une notation de routine. - Manuel — aucune clé définie. La page renvoie un prompt entièrement formé que vous pouvez coller dans Claude Code, ChatGPT, ou tout autre LLM.
Output sections (canonical career-ops.org A-F)
Réalignement v1.15.0. Les lettres de blocs correspondent désormais au schéma canonique career-ops.org. Avant la v1.15, les rapports utilisaient A–G (avec
C=Risks,F=Verdict,G=Legitimacy) ; nous les affichons toujours tels quels pour la rétrocompatibilité, mais les nouveaux rapports émettent A–F avec la sémantique canonique ci-dessous. Score et Légitimité vivent désormais dans l’en-tête du rapport (score: 4.2/5,legitimacy: High|Medium|Low).
A. Role Summary — récap en 3 puces (risques signalés en ligne).
B. CV Match — top 3 compétences présentes + top 3 manquantes.
C. Strategy — recommandation : postuler maintenant / contacto d’abord /
deep d’abord / passer. Était Risks avant la v1.15.
D. Compensation — par rapport à votre target.comp_total_min_usd
(legacy) ou compensation.target_range (canonique).
E. Personalization — angle à privilégier, cadrage par archétype,
accroches à mentionner dans la lettre / la prise de contact. Était
Application Strategy avant la v1.15.
F. STAR stories — 1 à 3 blocs S-T-A-R prêts à coller adaptés au poste.
Était Verdict (score brut) avant la v1.15 ; le score apparaît désormais
dans l’en-tête du rapport aux côtés de legitimacy.
Saving the report
Cliquez sur 💾 Save report (ou utilisez le toggle de sauvegarde dans
l’appel API) pour persister le markdown dans
reports/<date>-<company>-<role>.md. L’en-tête analysé du rapport (Score /
Légitimité / URL) apparaît sur la page Reports et le Dashboard.
Batch-evaluate when you have 10+ JDs
Pour une seule offre, cette page #/evaluate est le bon outil. Pour 10+ URL
en file dans le pipeline, le clic par offre est impraticable — sautez à la
sous-section Batch evaluate du §14 (lancement de
./batch/batch-runner.sh depuis le parent), laissez-le travailler la nuit,
puis revenez à #/reports / #/tracker pour les résultats. Flux complet :
batch-evaluate-offers.
10. Reports (#/reports)
Parcourez chaque évaluation enregistrée. Les cartes montrent le titre, la date, le drapeau de légitimité, et le score (codé couleur : vert ≥ 4.0, jaune ≥ 3.0, rouge en dessous).
Cliquez sur une carte pour lire le markdown complet. Pagination : 12 par page ; commandes en bas.
La vue rapport individuel a aussi :
- ← All reports — retour à la grille.
- 🔗 Open JD — ouvre l’offre d’origine dans un nouvel onglet.
11. Tracker (#/tracker)
Le CRM. Une ligne par candidature ; vit dans data/applications.md sous
forme de tableau GitHub-Flavored Markdown.
Status flow
Evaluated → Applied → Responded → Interview → Offer / Hired / Rejected
/ Discarded / SKIP.
Hired (v1.118.0) est l’état final heureux — l’offre a été acceptée. Le tracker le marque d’un badge de célébration et l’accueille avec une bannière « poste décroché ».
La liste blanche de statuts est imposée côté serveur ; envoyer autre chose
dans un POST /api/tracker retombe sur Evaluated. La transition canonique
Evaluated → Applied est automatique quand vous confirmez Submitted. à la
fin de /career-ops apply (voir §14).
Column layout
| Column | What it is |
|---|---|
# |
Auto-numéroté, complété de zéros (001, 002, …). |
Date |
Date ISO (YYYY-MM-DD). Par défaut aujourd’hui. |
Company |
Texte libre. Les barres verticales (|) et les retours à la ligne sont échappés automatiquement. |
Role |
Idem. |
Score |
Format N/5 (p. ex. 4.2/5). |
Status |
Enum en liste blanche. |
PDF |
✅ une fois que generate-pdf.mjs a réussi pour cette ligne. |
Report |
Lien markdown vers le reports/*.md correspondant. |
Notes |
Texte libre, plafonné à 200 caractères. |
Filters
- Menu déroulant Status.
- Menu déroulant Score —
≥ 4.0(haut),≥ 3.0(moyen),< 3.0(bas). - Search — correspondance de sous-chaîne sur entreprise + poste.
Chaque filtre réinitialise le paginateur à la page 1. 25 lignes par page.
Maintenance buttons
- ▶ Normalize lance
normalize-statuses.mjs— recanonise l’orthographe des statuts (applied→Applied,interview→Interview). - ▶ Dedup lance
dedup-tracker.mjs— retire les doublons insensibles à la casse par(company, role). - ▶ Merge lance
merge-tracker.mjs— récupère les entrées en attente debatch/tracker-additions/*.tsv(où le flux batch du parent dépose les candidatures soumises via l’assistant Apply). Déduplique et archive les fichiers traités versbatch/tracker-additions/merged/. Voir batch-evaluate-offers pour le flux batch amont.
Adding rows
POST /api/tracker — corps { company, role, score?, status?, url?, reportSlug?, notes?, date? }. Dédup par (company, role) insensible à la
casse. Depuis l’UI, la page Evaluate propose un bouton « Add to tracker »
après une notation réussie.
12. Deep research (#/deep)
Générez un dossier d’entreprise structuré : aperçu, culture d’ingénierie, actualités récentes, sentiment Glassdoor, processus d’entretien, points de levier de négociation, trois questions intelligentes à poser au recruteur.
Input
Deux champs — nom de l’entreprise et (optionnel) poste. Le modèle de mode
(modes/deep.md) est ce qui façonne la structure.
Output paths
Même chaîne de repli qu’Evaluate :
- Anthropic live (préféré) —
bundleProjectContextinline cv + profile_shared.md+deep.md. Sortie : 10–30 Ko de markdown ancré enregistré dansinterview-prep/<company>-<role>.md.
- Gemini live — invocation
gemini-eval.mjs. Même cible d’enregistrement. - Prompt manuel — la page vous remet un prompt prêt pour Claude Code (qui a WebFetch + WebSearch et peut faire de vraies recherches).
Tips
- Anthropic sur
claude-sonnet-4-6renvoie typiquement ~13 Ko de texte utile en 1–3 minutes par appel. - Le SDK Anthropic n’a pas de recherche web intégrée. Pour les postes où il vous faut des actualités fraîches + le sentiment Glassdoor, collez le prompt manuel dans Claude Code et laissez-le utiliser son outil WebFetch.
- Les exécutions live sont facturées ; un appel de recherche approfondie Sonnet 4.6 coûte ≈ 0,30–0,50 $.
13. Mode prompts (the seven /#/<mode> pages)
Tableau de cadence (v1.117.0). La page de relance s’ouvre désormais sur un tableau de cadence déterministe alimenté par le followup-cadence.mjs du parent : urgence par candidature (🔴 urgent / 🟠 en retard / 🟡 en attente / 🔵 froid) avec les jours avant la prochaine étape, plus un bouton Semer les dates de relance qui fixe une première date pour chaque ligne Applied (followup-seed.mjs --backfill). Sans les scripts du parent, le tableau affiche honnêtement « indisponible ».
Sept générateurs de prompts : idées Project, plans Training, e-mails
Follow-up, évaluations Batch, Outreach aux recruteurs,
fiches Interview prep, et rétrospectives Patterns. Chacun enveloppe
un modèle modes/<slug>.md précis :
| Page | Slug | Purpose |
|---|---|---|
#/project |
project |
Adapter un projet de portfolio à un poste cible. |
#/training |
training |
Analyse de l’écart de compétences → curriculum. |
#/followup |
followup |
Brouillon d’e-mail après entretien. |
#/batch |
batch |
Prompt d’évaluation par lot multi-offres. |
#/contacto |
contacto |
Message de prise de contact à un recruteur / une recommandation. |
#/interview-prep |
interview-prep |
Fiche de préparation pour un tour d’entretien précis. |
#/patterns |
patterns |
Analyse réflexive « Quels schémas m’ont fait réussir ? ». |
Shared shape
Chaque page a un petit formulaire (les champs sont propres au mode), un bouton ▶ Generate prompt (manuel), et — quand une clé Anthropic ou Gemini est présente — un bouton ⚡ Run live qui passe en primaire.
Cliquer sur ▶ Generate prompt renvoie le prompt assemblé avec vos
valeurs de formulaire converties en JSON dans un bloc User-supplied context:, suivi du modèle modes/<slug>.md verbatim. Copiez-collez dans le
LLM de votre choix.
Cliquer sur ⚡ Run live envoie le même prompt à Anthropic (ou Gemini),
avec cv.md + profile.yml + _shared.md inlinés via
bundleProjectContext. Le résultat est rendu sur la page, copiable, et
téléchargeable en .md.
Les sept pages sont une liste blanche explicite — les modes ayant une route
dédiée (oferta → Evaluate, deep → Deep research) et les modes que le
projet parent ne prend en charge que dans Claude Code (apply, scan,
pipeline, tracker, pdf, latex, ofertas, auto-pipeline) restent
délibérément hors de cette UI.
14. Apply checklist (#/apply)
Une fois que vous avez décidé de postuler, cette page Apply génère une
checklist de soumission pour l’étape de candidature réelle. Elle ne
remplit PAS les formulaires automatiquement — ce flux reste dans
/career-ops apply dans Claude Code, qui utilise Playwright dans le projet
parent.
SPA checklist mode (#/apply)
La checklist de la SPA est pour les utilisateurs qui préfèrent remplir le formulaire à la main sans invoquer Playwright. Elle couvre :
- Lancez
/career-ops apply <url>dans Claude Code pour lire le formulaire via Playwright (sautez cette étape si vous remplissez à la main). - Vérifiez que l’offre est toujours en ligne (
check-liveness.mjs). - Confirmez que le CV est le plus récent (
cv-sync-check.mjs, puis PDF si score ≥ 4.0). - Adaptez la lettre de motivation / la réponse « Pourquoi nous ? » avec les
proof points STAR+R de
cv.md. - Répondez honnêtement aux questions EEO / parrainage / date de début.
- Enregistrez les réponses remplies dans
interview-prep/{company}-{role}.mdavant de soumettre. - NE JAMAIS soumettre automatiquement — c’est vous (l’humain) qui cliquez sur le bouton final.
- Après la soumission : ajoutez une ligne à
data/applications.md(ou écrivez un TSV dansbatch/tracker-additions/).
Manual fill vs Playwright-assisted
Deux voies pour la soumission réelle :
- Manuel — ouvrez la page carrières dans un onglet de navigateur normal, suivez la checklist SPA ci-dessus, copiez-collez les réponses. Pas besoin de Playwright. À utiliser quand le formulaire est court ou que vous n’avez pas Chromium installé.
- Assisté par Playwright — lancez
/career-ops apply <company>dans Claude Code (projet parent). Playwright ouvre son propre navigateur, lit chaque champ du formulaire, renvoie des brouillons de réponses numérotés. C’est toujours vous qui cliquez sur Envoyer. À utiliser quand le formulaire est long, dynamique, ou que vous voulez la traçabilité des questions et réponses.
Full CLI apply flow (apply-for-a-job)
Prérequis :
- Lancez d’abord
/career-ops pipelinepour que l’offre ait un rapport d’évaluation sousreports/. La commande apply dépend d’une évaluation existante ; sans elle, lancez d’abord le pipeline. - Ayez le rapport et le profil chargés.
- Recommandé : Playwright installé (
npx playwright install chromium— voir Playwright Setup ci-dessous). Retombe sur WebFetch (aperçu de formulaire texte seul, sans remplissage par clic) s’il manque.
Flux numéroté (8 étapes canoniques) :
-
Lancez la commande avec le nom de l’entreprise :
/career-ops apply <company>Exemple :
/career-ops apply Anthropic. Sans argument, fournissez une capture d’écran du formulaire, le texte du formulaire collé, ou l’URL de candidature au tour suivant. -
Localisez le rapport. Le système trouve l’évaluation correspondante dans
reports/(celle créée par/career-ops pipelineou#/evaluateplus tôt). -
Ouvrez le formulaire. Playwright lance une fenêtre de navigateur automatiquement — vous ne l’ouvrez PAS vous-même.
-
Lisez les champs. Le système lit et analyse chaque champ du formulaire (libellé, type, requis, options des sélecteurs).
-
Générez les réponses. career-ops crée des réponses sur mesure pour chaque champ, basées sur votre profil, vos proof points et le poste.
-
Renvoyez la liste numérotée. Vous recevez des réponses ordonnées pour correspondre à la disposition du formulaire — champs simples (nom, e-mail) d’abord, champs libres (lettre de motivation, « Pourquoi nous ? ») en dernier. Les éléments marqués pointent ce qui requiert une attention humaine — ancrage salarial, détails de CV manquants, questions optionnelles.
-
Remplissage manuel. Vous copiez-collez chaque réponse dans le champ correspondant. Cette étape est manuelle, pas automatisée. Vous revoyez chaque réponse d’abord.
-
L’utilisateur soumet. Vous cliquez sur Envoyer vous-même. career-ops ne clique jamais sur Envoyer. Confirmez la complétion en tapant dans le chat :
Submitted.
Mises à jour automatiques sur Submitted. :
- Le statut bascule
Evaluated → Applieddansdata/applications.md. - Les réponses remplies persistent en Section G du rapport pour référence future.
Passation au tracker :
/career-ops tracker
Suivez le statut de tout votre pipeline, quel que soit le score du poste.
Batch evaluate (batch-evaluate-offers)
Quand vous avez 10+ offres à noter d’un coup (le #/evaluate un-par-un de
la SPA est impraticable pour ce volume), utilisez le batch runner en CLI.
Fichier d’entrée — batch/batch-input.tsv (séparé par tabulations) :
| Column | Purpose |
|---|---|
id |
Numéro séquentiel unique |
url |
Lien complet de l’offre |
source |
Plateforme d’origine (LinkedIn, Greenhouse, etc.) |
notes |
Détail contextuel optionnel |
Exemple de ligne :
1<TAB>https://jobs.example.com/senior<TAB>LinkedIn<TAB>
Drapeaux de ./batch/batch-runner.sh :
--dry-run— Prévisualise les offres en attente sans évaluation. Lancez toujours ceci d’abord pour valider le TSV.--parallel N— Lance N workers simultanément (1, 2 ou 3 recommandés).--min-score X.X— Ne persiste pas les offres sous le seuil. Utile pour ne garder que les rapports des postes à forte adéquation.--retry-failed— Retraite uniquement les offres en erreur au run précédent (échecs réseau, rate limits).--max-retries N— Réessaie les offres échouées jusqu’à N fois (défaut : 2).--model NAME— Modèle Claude passé àclaude -p --model(parent career-ops 1.8.0, #504). Non défini = votre défaut d’abonnement Claude Max. Utilisez un modèle moins cher pour les gros lots, p. ex.claude-sonnet-4-6. Exposé dans#/batchcomme le champ Model (web-ui 1.31.0).--start-from N— Saute les IDs d’offre sous N (reprend un lot partiellement traité). Exposé dans#/batchcomme le champ Start from # (web-ui 1.31.0).
Séquence standard :
-
Éditez
batch/batch-input.tsv— une ligne par offre. -
Dry-run (recommandé en premier) :
./batch/batch-runner.sh --dry-run -
Run — séquentiel ou parallèle :
./batch/batch-runner.sh # one at a time ./batch/batch-runner.sh --parallel 2 # two concurrent ./batch/batch-runner.sh --parallel 3 # three concurrent ./batch/batch-runner.sh --parallel 2 --min-score 4.0 # only persist high-fit -
Réessayez les échecs (réseau / rate-limit) :
./batch/batch-runner.sh --retry-failed --max-retries 3 -
Les rapports atterrissent dans
reports/sous{id}-{company}-{YYYY-MM-DD}.md. Les lignes de résumé s’ajoutent àbatch/tracker-additions/. -
Fusionnez dans le tracker :
node merge-tracker.mjs # apply the batch additions node merge-tracker.mjs --dry-run # preview the mergeLa commande de fusion déduplique les entrées et archive les fichiers traités vers
batch/tracker-additions/merged/.
La SPA expose les rapports résultants sous #/reports (paginés, pastille de
score colorée) et les lignes du tracker sous #/tracker — exactement comme
si vous aviez ajouté chacun via #/evaluate. Associez avec le bouton de
maintenance ▶ Merge sur #/tracker si vous préférez ne pas descendre au
CLI.
Playwright setup (set-up-playwright)
Requis pour deux fonctionnalités career-ops :
- Remplissage de formulaire dans
/career-ops apply(étape 3 ci-dessus — Playwright ouvre le navigateur, lit les libellés des champs, suggère des réponses). - Génération PDF via
/career-ops pdfet le bouton 📄 Generate PDF de la SPA sur#/cv/#/reports/:slug/#/evaluate/#/deep/#/interview-prep.
Repli quand Playwright manque : le flux apply retombe sur WebFetch (aperçu de formulaire texte seul, sans remplissage par clic). La génération PDF échoue simplement.
Setup principal (à lancer depuis la racine parente career-ops) :
# Install Chromium for Playwright
npm install
npx playwright install chromium
# Register the Playwright MCP so Claude Code can drive forms
claude mcp add playwright npx @playwright/mcp@latest
# Verify all three components (Chromium, Playwright lib, MCP)
npm run doctor
Enregistrement MCP alternatif — ajoutez à .claude/settings.local.json :
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["-y", "@playwright/mcp@latest"]
}
}
}
Notes de comportement :
- Headless par défaut. Playwright opère silencieusement. Pour voir le
navigateur en action, dites à Claude
open up with playwright the browser and fill out the entire form. - Trois rôles dans un seul paquet — l’installation npm de Playwright
vous donne la bibliothèque d’automatisation de navigateur, le moteur de
rendu PDF pour
/career-ops pdf, et (via le MCP) le flux de remplissage de formulaire dans Claude Code. - Vérifiez avant de vous y fier —
npm run doctorconfirme que les trois sont opérationnels. La page Health de la SPA expose une vérificationPlaywright (parent node_modules)qui échoue vite s’il manque.
15. Interview preparation
C’est la phase post-recherche, pré-entretien. Trois artefacts de cette application convergent :
- Fichiers de recherche approfondie enregistrés sous
interview-prep/, un par paire entreprise-poste que vous avez lancée. Parcourez depuis la page Deep research ou directement via/api/interview-prep. - Mode Patterns (
#/patterns) — génère un prompt introspectif : « sur mes N derniers entretiens / offres / refus, quels schémas se maintiennent ? ». Utile une fois 5+ lignes de tracker accumulées. - Mode Interview-prep (
#/interview-prep) — pré-remplit une fiche pour un tour à venir précis (comportemental, technique, system design). La sortie va dans le même dossierinterview-prep/.
Recommended workflow
Pour chaque entretien que vous avez à l’agenda :
- Relancez Deep (ou ouvrez le fichier enregistré) la veille.
#/interview-prep— générez une fiche pour le tour précis. Collez-la dans vos notes.- Tours system design / coding — ouvrez
#/traininget demandez une remise à niveau ciblée de 30 minutes sur le sous-système précis que l’offre met en avant. - Tours de rémunération — ouvrez le fichier de recherche approfondie, sautez à « Negotiation leverage points ». Apportez 2–3 points de données précis (fourchette Glassdoor, levée de fonds récente, offre comparable ailleurs).
- Tours comportementaux — tirez les histoires STAR+R de votre
cv.mdqui atterrissent en section B du rapport Evaluate d’origine.
Après l’entretien, immédiatement :
- Mettez à jour la ligne du tracker : statut →
Responded(puisInterview,Offer, etc.). - Lancez
#/followuppour rédiger l’e-mail de remerciement. - Si vous avez de nouvelles infos (fourchette de rémunération, composition
d’équipe, surprise de stack technique), éditez le
interview-prep/<company>-<role>.mdenregistré avec## Post-round notespour que le vous-futur l’ait.
16. Activity log + Troubleshooting
Activity log (#/activity)
Journal d’audit de chaque requête modifiant l’état qui touche le serveur. Enregistre : ajouts au pipeline, écritures du tracker, sauvegardes de CV, sauvegardes d’offres, exécutions d’évaluation, exécutions de recherche approfondie, exécutions de scan, changements de config, exécutions de mode.
Les secrets (ANTHROPIC_API_KEY, GEMINI_API_KEY) sont caviardés à
l’entrée ; vous ne verrez jamais une vraie valeur de clé dans
data/activity.jsonl.
Filtrez par préfixe d’action (pipeline., cv., evaluate, scan.,
etc.). 25 lignes par page ; le serveur renvoie jusqu’aux 500 événements les
plus récents.
Troubleshooting
| Symptôme | Cause probable | Correctif |
|---|---|---|
Page Health rouge sur cv.md |
Premier lancement, le fichier n’existe pas encore | touch $CAREER_OPS_ROOT/cv.md puis rafraîchir. |
Health rouge sur Profile customized |
candidate.full_name dit encore Jane Smith |
Éditez config/profile.yml. |
hh.ru: HTTP 403 dans le journal de scan |
IP non russe, pas de (server uses default UA) |
Inscrivez-vous sur dev.hh.ru/admin, utilisez une IP / VPN russe. |
gemini-eval.mjs: ERR_MODULE_NOT_FOUND |
Dépendances du projet parent non installées | cd $CAREER_OPS_ROOT && npm install. |
| Erreurs Generate PDF | Playwright non installé dans le parent | cd $CAREER_OPS_ROOT && npx playwright install chromium. |
/career-ops apply dit « no report found » |
Le pipeline n’a jamais noté cette offre | Lancez /career-ops pipeline (ou #/evaluate) d’abord ; voir les prérequis du §14. |
batch-runner.sh: no such file |
Lancé depuis le mauvais répertoire | cd $CAREER_OPS_ROOT avant d’invoquer ./batch/batch-runner.sh. |
Le serveur signale EADDRINUSE: 4317 |
Ancienne instance toujours en cours | pkill -f 'node server/index.mjs' puis redémarrez. |
| Appel LLM live qui bloque > 2 min | Prompt énorme ou Anthropic lent | Vérifiez le drapeau Anthropic de /api/health ; le serveur plafonne en douceur les prompts à 200 Ko et renvoie 413. |
L’aperçu du pipeline montre (unsafe redirect) |
L’offre redirige vers une IP privée / loopback | C’est une fonctionnalité de sécurité (REVIEW-B1). La cible de redirection est rejetée et l’URL d’origine est inchangée. |
| Le texte d’une ligne de tracker casse le tableau | Barre verticale dans le nom d’entreprise avant la v1.9.1 | Mettez à jour vers la v1.9.1+ — les barres sont échappées de bout en bout (BF-1). |
npm test échoue sur un clone neuf |
Les tests supposent la disposition du projet parent | Utilisez CAREER_OPS_ROOT=$(mktemp -d) et amorcez des fixtures. |
Pour un diagnostic plus poussé : lancez ▶ Doctor sur la page Health, copiez la sortie, et cherchez le problème sur le tracker d’incidents à https://github.com/Fighter90/career-ops-ui/issues.
17. How to add a new job-portal source
career-ops-ui traite chaque site d’emploi comme un adaptateur — un fichier unique sous server/lib/sources/<slug>.mjs qui sait récupérer + normaliser les résultats d’un site. Depuis la v1.118.0, le registre server/lib/sources/ livre 59 adaptateurs — 54 anglais + 5 russes. L’ensemble anglais couvre les principaux ATS (Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday), les agrégateurs couvrant tout un board sélectionnés par un provider: explicite (RemoteOK, Remotive, We Work Remotely, NoDesk, Get on Board, Amazon, …), et les ATS par tenant auto-détectés depuis un hôte careers_url ou une URL api: explicite (BambooHR, Personio, Recruitee, Teamtailor, Avature, SAP SuccessFactors, …). La liste complète n’a jamais besoin d’être comptée à la main ici — elle est auto-découverte depuis server/lib/sources/ et affichée en direct dans le menu déroulant Source de #/scan. Voir le §5 pour le YAML et docs/portals-examples.md pour des entrées à copier-coller.
v1.69.0 (P-14) — auto-découverte par dépôt de fichier. Ajouter une 12e source est désormais un simple dépôt de fichier. Le registre (
server/lib/sources/registry.mjs) ne contient plus de liste gérée à la main — au démarrage il scanne ce dossier (readdirSync+import()dynamique) et collecte le blocexport const metade chaque*.mjs. Écrivez l’adaptateur, déclarez sonmeta, et il est immédiatement visible dans le scanner, dans le menu déroulant de filtre#/scanet dans le dispatcher RU — aucune modification deregistry.mjsrequise. (Les sources RU nécessitent toujours une ligne dans leportals.ymldu parent ; voir l’étape 5.)
Step 1 — Write the adapter
Créez server/lib/sources/<slug>.mjs. Deux patterns fonctionnent selon que
la source a une API JSON ou ne rend que du HTML :
Source adossée à une API (la plus propre — utilisez-la dès que le site a un endpoint de données ouvert) :
// server/lib/sources/example.mjs
const ENDPOINT = 'https://example.com/api/v1/vacancies';
const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...';
// v1.69.0 (P-14) — self-describing metadata. The registry auto-discovers
// this block at boot; THIS is what registers the source (see Step 2).
export const meta = {
value: 'example', // ← must equal job.source written below
label: 'Example.com', // ← shown in the #/scan filter dropdown
region: 'ru', // ← 'en' (ATS sweep) | 'ru' (regional dispatcher)
configKey: 'example', // ← RU only; the key used in portals.yml
};
export async function searchExample(query, opts = {}) {
const { onlyRemote = false, fetchImpl = fetch, signal } = opts;
const res = await fetchImpl(`${ENDPOINT}?text=${encodeURIComponent(query)}`, {
signal,
headers: { 'User-Agent': UA, Accept: 'application/json' },
});
if (!res.ok) {
const err = new Error(`Example: HTTP ${res.status}`);
err.status = res.status;
throw err;
}
const data = await res.json();
return (data.items || []).map(normalizeExample);
}
function normalizeExample(item) {
return {
id: `example-${item.id}`,
title: item.title || '',
company: item.company?.name || '',
url: item.url || '',
salary: item.salary || '',
location: item.location || '',
isRemote: !!item.remote,
workplaceType: item.remote ? 'Remote' : 'Onsite',
relocates: false,
date: item.posted_at || '',
snippet: (item.description || '').slice(0, 240),
source: 'example', // ← must match the registry `value` exactly
};
}
Source par scraping HTML (quand il n’y a pas d’API — voir
getmatch.mjs et
geekjob.mjs pour des exemples
complets) :
const BASE = 'https://example.com';
export async function searchExample(query, opts = {}) {
const { fetchImpl = fetch, signal } = opts;
const res = await fetchImpl(`${BASE}/vacancies?q=${encodeURIComponent(query)}`, {
signal,
headers: { 'User-Agent': UA, Accept: 'text/html' },
});
if (!res.ok) {
throw Object.assign(new Error(`Example: HTTP ${res.status}`), { status: res.status });
}
return parseExampleCards(await res.text());
}
export function parseExampleCards(html) {
// …regex-based card extraction. Return [] on parse failure (DON'T throw):
// a healthy 200 with no parseable cards is "no results", not "error",
// so the multi-source scanner can keep going.
}
Trois contrats que chaque adaptateur DOIT honorer :
- Exporter un bloc
metavalide (voir l’étape 2). Sans lui, le registre ignore silencieusement le fichier (unconsole.warnau démarrage) et la source n’apparaît jamais. - Accepter
{ onlyRemote, fetchImpl, signal }dansopts.fetchImplest ce qui rend les adaptateurs testables sans réseau ;signalest requis pour la propagation de la déconnexion du client (REVIEW-B3). - Renvoyer des enregistrements à la forme commune —
{ id, title, company, url, salary, location, isRemote, workplaceType, relocates, date, snippet, source }, oùsourcecorrespond àmeta.value.
Step 2 — Declare the adapter’s meta (auto-registration)
C’est toute l’étape d’enregistrement. Vous ne modifiez pas registry.mjs.
Assurez-vous simplement que l’adaptateur exporte un bloc meta — le registre
l’auto-découvre au démarrage :
// at the top of server/lib/sources/example.mjs
export const meta = {
value: 'example', // job.source value AND #/scan option.value
label: 'Example.com', // display label in the dropdown
region: 'ru', // 'en' | 'ru'
configKey: 'example', // RU only — key in portals.yml::russian_portals.sources
};
Comment la découverte le valide (un fichier échouant à une règle est ignoré, avec un
avertissement [sources/registry], afin qu’une branche à moitié migrée reste diagnosticable) :
value— chaîne non vide. DOIT correspondre àjob.sourcede votre adaptateur.label— chaîne non vide.region— exactement'en'ou'ru'; toute autre valeur est rejetée.configKey— requis pourregion: 'ru', ignoré pour'en'.
region: 'en' rejoint la passe ATS (auto-découverte depuis les patterns d’URL
tracked_companies) ; region: 'ru' rejoint le dispatcher régional. L’API publique
(SOURCES, SOURCES_BY_REGION, RU_CONFIG_KEYS, getRegionalSources) est
reconstruite à partir de chaque meta découvert, dans l’ordre en d’abord puis ru,
par ordre alphabétique du label au sein de chaque région — l’ordre du menu déroulant
reste ainsi stable pour les utilisateurs.
Step 3 — Wire into the dispatcher (RU only)
Les sources ATS EN s’auto-découvrent depuis les patterns d’URL
tracked_companies — aucun câblage supplémentaire. Pour les sources RU,
ouvrez server/lib/ru-scanner.mjs,
trouvez la table RU_DISPATCH, et ajoutez une ligne :
import { searchExample } from './sources/example.mjs';
// …
const RU_DISPATCH = {
// …existing…
example: { label: 'example.com', search: searchExample },
};
La boucle du dispatcher appelle entry.search(query, opts) pour chaque clé
présente dans cfg.sources. Aucun autre changement de code nécessaire.
Step 4 — Test (mocked, never live)
Déposez un fichier sous tests/sources-<slug>.test.mjs. Le réseau réel est
interdit dans les tests (contrat d’isolation CI) :
import test from 'node:test';
import assert from 'node:assert/strict';
import { searchExample } from '../server/lib/sources/example.mjs';
test('searchExample normalizes one record', async () => {
const fetchImpl = async () =>
new Response(
JSON.stringify({ items: [{ id: 1, title: 'Backend Engineer' }] }),
{ status: 200, headers: { 'content-type': 'application/json' } }
);
const out = await searchExample('q', { fetchImpl });
assert.equal(out.length, 1);
assert.equal(out[0].source, 'example');
});
Step 5 — Enable in your portals.yml
Le portals.yml du projet parent est la config détenue par l’utilisateur.
Ajoutez le configKey de la nouvelle source au tableau :
russian_portals:
sources: ["hh", "habr", "trudvsem", "getmatch", "geekjob", "example"]
area: 113
per_page: 50
only_remote: false
queries:
- "Senior PHP"
- "Senior Go"
Rechargez #/scan dans le navigateur. Le menu déroulant de filtre par source
récupère la nouvelle entrée automatiquement (source unique de vérité via
GET /api/scan/sources →
registry.mjs). Le bouton
🌐 Scan inclut désormais la nouvelle source à chaque passe régionale.
Reference adapters (mirror these for new sources)
| Fichier adaptateur | Type | Notes |
|---|---|---|
hh.mjs |
API JSON | Adaptateur API RU canonique ; repli d’UA géo-conscient. |
trudvsem.mjs |
API JSON | Open-data du gouvernement russe ; pas de barrière IP. |
habr.mjs |
Scrape HTML | Site tech russe ; parseur de cartes par regex. |
getmatch.mjs |
Scrape HTML | Parseur défensif, [] en cas d’échec de parsing. |
geekjob.mjs |
Scrape HTML | Même style défensif que GetMatch. |
greenhouse.mjs |
API JSON | Adaptateur ATS EN canonique ; utilise le pattern d’URL tracked_companies. |
Common pitfalls
- Oublier l’export
meta. Depuis la v1.69.0, le blocmetaest la seule chose qui enregistre une source. Pas demeta(ou unmetamalformé) = le fichier est silencieusement ignoré au démarrage avec un seul avertissement[sources/registry] <file> has no valid \export const meta` — skipped`, et la source n’atteint jamais le menu déroulant. Vérifiez le journal du serveur si un tout nouvel adaptateur n’apparaît pas. - Décalage du champ
source. La chaîne écrite par votre adaptateur DOIT correspondre exactement àmeta.value. Si elles divergent, le menu de filtre#/scanmontrera la source mais la sélectionner filtrera toutes les lignes (car le test d’égalité estr.source === fs). - Lever une exception en cas d’échec de parsing. Les scrapers HTML
DOIVENT renvoyer
[]sur un 200 sain sans cartes analysables. Lever une exception casse la boucle du dispatcher multi-source — une mauvaise structure HTML tue toutes les autres sources pour la même requête. - Oublier
fetchImpl/signal. Sans eux, votre adaptateur ne peut pas être testé unitairement sans toucher le réseau live, et les déconnexions du client ne se propagent pas (la requête en arrière-plan reste vivante après que l’utilisateur ferme l’onglet). - Faire confiance à
tracked_companiespour le RU. Cette liste est uniquement pour les sources ATS EN. Les adaptateurs RU se pilotent eux-mêmes depuisrussian_portals.queries— pas d’entrées par entreprise.
18. Notifications (🔔 in the top bar)
v1.58.34 — chaque toast qui apparaît dans le coin inférieur droit est aussi capturé dans un journal en mémoire (plafond 50, le plus ancien éjecté). Cliquez sur la cloche 🔔 dans la barre du haut pour ouvrir le tiroir Notifications glissant à droite et relire tout ce que vous avez manqué. Le journal est par-onglet, par-session — fermer l’onglet l’efface.
Le tiroir ne s’ouvre que quand vous cliquez sur la cloche (ou l’activez avec Entrée / Espace quand elle a le focus clavier). Il n’apparaît jamais de lui-même. Le badge rouge sur la cloche compte les entrées que vous n’avez pas vues depuis la dernière ouverture ; ouvrir le tiroir efface le badge.
Notification categories
| Catégorie | Quand elle se déclenche | Indice visuel |
|---|---|---|
| Success | Saved, Copied, Refreshed, scan terminé, CV importé, actions de checklist apply (« Copied unchecked », « Reset »), profil enregistré, URL ajoutée au pipeline |
bordure gauche verte dans le tiroir ; fond de toast vert |
| Error | échec de validation d’URL (doit commencer par http:// / https://, pas de caractères script/template), erreurs d’API avec le postfixe (METHOD /path · HTTP NNN), échecs réseau (serveur en panne), doublons pipeline-400, sortie non-zéro de doctor / verify-pipeline |
bordure gauche rouge ; fond de toast rouge ; postfixe technique rangé dans le bloc Details <details> (U-4 / v1.58.24) |
| Info / progress | Running doctor.mjs…, Running verify-pipeline.mjs…, Refreshing…, Loading…, Generating prompt…, lignes de progression de scan |
bordure gauche grise ; fond de toast par défaut |
Chaque entrée du tiroir affiche :
- Horodatage (
HH:MM:SSlocalisé selon la langue active de la SPA). - Message (la phrase humaine, avec le postfixe technique retiré de l’en-tête selon U-4).
- Details (quand présent — le postfixe
(METHOD /path · HTTP NNN)de l’appel API ou tout autre aparté technique, en monospace).
What is NOT a notification
- La modale de résultat Doctor / verify-pipeline (stdout / stderr complet) — c’est une modale, pas un toast, et non journalisée.
- Les lignes de journal SSE sur
#/scanet#/auto— celles-ci défilent dans le corps de la page, pas dans le pipeline de toasts. - Les états de chargement avec spinner seul (ceux-ci utilisent
UI.withSpinnersans toast).
Keyboard
- Clic ou focus + Entrée / Espace sur la cloche → ouvre le tiroir.
- Échap, clic sur le bouton de fermeture ×, ou re-clic sur la cloche → ferme le tiroir ; le focus revient à la cloche.
- Tab quand le tiroir est ouvert → parcourt le bouton de fermeture et tout détail focalisable à l’intérieur ; le tiroir est
aria-modal="false", donc Tab ne piège pas (vous pouvez toujours atteindre le reste de la page).
19. Localizing the app into your language
L’interface est livrée en 9 langues (English, Español, Français, Português, 한국어, 日本語, Русский, 简体中文, 繁體中文). Chaque libellé à l’écran provient d’un dictionnaire de traduction, et vous pouvez ajouter ou corriger une langue sans toucher à la logique de l’application.
Où vivent les traductions. Depuis la v1.60.0, chaque langue est son propre fichier sous public/js/lib/locales/ — i18n-dict.en.js, i18n-dict.es.js, i18n-dict.fr.js, i18n-dict.ru.js, et ainsi de suite — une simple liste de paires 'key': 'text'. Un i18n-dict.aliases.js partagé permet aux clés qui doivent toujours se lire à l’identique (un libellé de barre latérale et son titre de page) de pointer vers une seule traduction. i18n-dict.js les fusionne toutes au chargement de la page ; vous ne l’éditez jamais.
Corriger ou ajouter une phrase. Ouvrez le fichier de votre langue, trouvez la clé (p. ex. 'nav.scan') et éditez le texte. Pour ajouter un libellé tout neuf, ajoutez la même clé à tous les 9 fichiers de langue avec la valeur traduite, puis référencez-la dans la page via t('your.key'). Lancez npm test — il échoue si une langue manque la clé, donc rien n’est livré à moitié traduit.
Ajouter une langue entièrement nouvelle. Copiez i18n-dict.en.js vers i18n-dict.<code>.js, traduisez chaque valeur, puis enregistrez le code dans i18n.js (la liste de langues + l’auto-détection du navigateur), dans l’assembleur i18n-dict.js, et ajoutez une ligne <script> dans index.html. La checklist complète — y compris le snapshot de test et les fichiers compagnons help / README — est dans docs/LOCALIZATION.md.
Bon à savoir. Le sélecteur de langue est dans le pied de la barre latérale ; votre choix est mémorisé par navigateur. Les messages de diagnostic du serveur restent en anglais à dessein (pour que les logs se lisent de façon cohérente) — seule l’interface à l’écran est traduite.
Voir docs/LOCALIZATION.md dans le dépôt pour le guide de localisation complet, étape par étape.
20. Statistiques par rôles cibles (#/stats)
La page Analytics → Statistiques par rôle cible transforme les données éparses que vos scans collectent déjà en une image du marché pour les rôles que vous visez réellement : nombre d’offres et niveaux de salaire par pays, plus une tendance que vous pouvez suivre dans le temps. Rien n’est inventé : elle agrège seulement ce que les scanners ont trouvé, et elle est honnête sur la finesse de l’échantillon.
D’où viennent les chiffres
- Les rôles cibles sont lus depuis votre Profil (
config/profile.yml→ target roles), jamais codés en dur. Définissez-les d’abord sur#/profile; sans rôles, la page affiche une invite « définissez vos rôles cibles » au lieu de graphiques vides. - Les offres proviennent de votre dernier scan (lancez-en un d’abord sur
#/scan). Le lieu de chaque offre est associé à un pays (le même détecteur que le filtre pays du scan) et sa chaîne de salaire est analysée puis normalisée en USD via une table de change approximative. - Tout est agrégé dans votre navigateur : aucune donnée ne quitte votre machine, et la seule chose que la page écrit jamais est un instantané que vous enregistrez explicitement.
Lire les graphiques
- Offres par pays : combien d’offres correspondantes se trouvent dans chaque pays. Utilisez les filtres Rôle et Pays en haut pour restreindre à un seul rôle cible ou à un seul pays.
- Salaire médian par pays (USD) : le salaire médian analysé par pays. Seules les offres avec un salaire exploitable sont comptées ; la taille de l’échantillon est indiquée à côté du graphique, et les montants sont convertis à des taux approximatifs, alors lisez-le comme indicatif, non exact. Un
¥seul (ambigu entre le yen japonais et le yuan chinois) est écarté plutôt que deviné, pour éviter une forte distorsion de FX. - Lorsque le scan actuel n’a aucun salaire exploitable, le graphique des salaires le signale au lieu d’inventer des chiffres.
Enregistrer des instantanés et suivre la tendance
- Cliquez sur Enregistrer l’instantané pour ajouter l’agrégat actuel à
data/role-stats.jsonl. Chaque instantané est horodaté sur le serveur ; les instantanés sont la seule chose que cette page écrit et ils ne touchent jamais votre CV ni votre profil. - Le graphique de tendance trace le nombre d’offres au fil de vos instantanés enregistrés — enregistrez-en un régulièrement (par exemple après chaque scan hebdomadaire) pour observer comment le marché de vos rôles cibles évolue dans le temps.
21. Votre document de deux pages — adéquation candidat-marché (#/two-pager)
La plupart de career-ops-ui demande « cette offre correspond-elle à mon CV ? ». Le document de deux pages répond à l’autre moitié : « cette offre correspond-elle à ce que je veux vraiment ? ». Il s’inspire du « document de deux pages de Mnookin » de Never Search Alone — une déclaration brève, à la première personne, de ce qui vous donne de l’énergie, de ce que vous exigez et de ce que vous n’accepterez pas. Ouvrez-le depuis Configuration → Document de deux pages 🎯.
Remplissage IA + export (v1.100). L’« ✨ assistant de remplissage IA » remplit désormais tous les champs en direct depuis votre CV (relisez, puis Enregistrez) ; 👁 Aperçu et export rend le two-pager et l’exporte en Markdown, PDF ou DOCX.
Ce que vous remplissez
- Qui je suis — quelques phrases à la première personne sur votre parcours et le type de rôle où vous excellez.
- Environnement cible — la taille, le stade et la culture d’entreprise que vous recherchez.
- Cinq listes de chips — tapez et appuyez sur Enter (ou virgule) pour ajouter chaque élément, cliquez sur × pour le retirer :
- Ce que j’adore — sources d’énergie (télétravail, autonomie, greenfield, mentorat…).
- Indispensables — exigences fermes (un salaire plancher, un pays, une stack…).
- Ce que je déteste — sources d’épuisement (astreintes, réunions interminables, legacy uniquement…).
- Rédhibitoires — non absolus (présentiel uniquement, pas de parrainage de visa, en dessous d’un chiffre…).
- Non négociables — limites (localisation, télétravail, salaire plancher…).
Cliquez sur Enregistrer le document de deux pages pour le conserver. Il est écrit dans la couche utilisateur de votre projet parent career-ops dans config/two-pager.yml, donc — comme votre CV et votre profil — il n’est jamais écrasé lorsque vous mettez le système à jour.
L’assistant de remplissage IA
Vous ne savez pas comment le formuler ? Cliquez sur ✨ Assistant de remplissage IA. Il construit un prompt prêt à exécuter (le format Mnookin, avec votre CV et votre profil intégrés) et l’affiche dans une boîte de dialogue. Exécutez ce prompt dans n’importe quel LLM, puis collez les champs YAML obtenus dans le formulaire. L’assistant n’utilise que votre propre CV et profil — il n’invente jamais de faits à votre sujet, et aucun appel d’API en direct n’est effectué depuis ce bouton.
Le score d’adéquation avec ce que vous voulez
Une fois que vous avez enregistré un document de deux pages, chaque offre sur #/scan obtient un petit badge ◎ N (0–100). Il compare le type de travail (télétravail/hybride/présentiel), le pays, le salaire plancher et la relocalisation de chaque offre à votre document de deux pages — un badge vert signifie une bonne adéquation, un rouge signifie qu’un critère rédhibitoire s’est déclenché. Survolez pour voir les détails (✓ ce qui a correspondu, ✗ quel critère rédhibitoire a été violé).
Il est délibérément honnête : lorsqu’une offre ne donne aucun signal comparable (par exemple si vos préférences sont toutes du texte libre qu’une ligne de scan ne peut confirmer), aucun badge n’est affiché du tout — le système n’invente jamais de chiffre. La violation d’un critère rédhibitoire ferme pèse plus lourd qu’une détestation légère de la même chose. Au-delà du badge, votre document de deux pages enregistré est intégré à chaque évaluation par LLM, de sorte que vos préférences déclarées façonnent aussi le verdict écrit, pas seulement l’adéquation CV-vs-offre.
22. Entretien simulé (#/mock-interview)
Lire la préparation d’entretien est une chose ; dire les réponses à voix haute en est une autre. La page Entretien simulé (ouvrez-la depuis Préparation d’entretien → Entretien simulé 🎤 dans la barre latérale) déroule une répétition tour par tour face à un poste précis, ancrée dans votre propre CV, profil, document de deux pages et banque d’histoires. Ce n’est pas une liste de questions toutes faites — l’intervieweur réagit à ce que vous dites réellement.
Démarrer une session
- Saisissez un poste cible (et éventuellement une entreprise). Collez aussi la description du poste si vous l’avez — les questions deviennent nettement plus précises.
- Cliquez sur Démarrer l’entretien. L’intervieweur ouvre avec une question ciblée, adaptée au poste et à votre parcours.
- Tapez votre réponse et cliquez sur Envoyer la réponse. Répétez autant que vous voulez — c’est une conversation, pas un questionnaire figé.
Ce que chaque tour vous apporte
Après chaque réponse, l’intervieweur répond en trois parties :
- Retour — ce qui a marqué (points forts) et ce qui manquait, formulé en termes STAR+R (Situation, Tâche, Action, Résultat, Réflexion). Il nomme la dimension précise que vous avez sautée.
- Score — un rapide
N/5avec une justification d’une ligne, pour que vous ressentiez la progression au fil d’une session. - Question suivante — une relance qui sonde délibérément la partie la plus faible de votre dernière réponse.
Tout est ancré dans vos vrais documents : cv.md, config/profile.yml, config/two-pager.yml et votre banque d’histoires STAR+R (interview-prep/story-bank.md) sont tous intégrés au prompt. L’intervieweur insistera sur les vraies lacunes mais n’invente jamais une expérience que vous n’avez pas. Si aucune clé LLM n’est configurée, la page vous remet un prompt prêt à l’emploi à coller dans n’importe quel assistant — le même repli honnête utilisé ailleurs dans l’app.
Enregistrer et revisiter les sessions
Cliquez sur Enregistrer la transcription pour conserver une répétition terminée. Elle est écrite dans la couche utilisateur de votre projet parent, à interview-prep/mock-{company}-{role}-{date}.md, de sorte qu’elle vit aux côtés de vos autres notes de préparation d’entretien et n’est jamais écrasée par les mises à jour du système. La liste Sessions enregistrées en bas de la page vous permet de rouvrir n’importe quelle transcription ou de la supprimer. Utilisez Nouvel entretien pour recommencer avec un poste différent.
23. Réseautage et recherche approfondie sur les entreprises (#/networking)
Postuler par la grande porte ne représente que la moitié du jeu — l’autre moitié consiste à connaître quelqu’un, ou au moins à savoir qui contacter et quoi dire. La page Réseautage (ouvrez-la depuis Recherche approfondie → Réseautage 🤝 dans la barre latérale) transforme une entreprise en un plan concret pour décrocher un entretien, ancré dans votre propre CV, votre profil et votre two-pager.
Construire un plan
- Saisissez une entreprise (obligatoire) et, en option, un poste et la description du poste. La description affine les accroches « pourquoi je conviens ».
- Cliquez sur Construire le plan. Avec une clé LLM, il s’exécute en direct et affiche le plan dans la page ; sans clé, il vous fournit un prompt prêt à coller dans n’importe quel assistant (le même repli honnête utilisé dans toute l’application — rien n’est inventé).
Ce que contient le plan
Le plan revient en quatre sections :
- Dossier de l’entreprise — un briefing serré sur ce que fait l’entreprise, des signaux récents qui méritent d’être cités, et deux ou trois accroches « pourquoi je conviens » tirées de votre parcours réel.
- Qui contacter — trois à cinq personas cibles (le responsable du recrutement de l’équipe, un recruteur interne, un ingénieur senior de l’équipe, une relation chaleureuse ou issue des anciens élèves) avec une chaîne de recherche LinkedIn concrète pour trouver chacun d’eux. Il n’invente jamais de vrais noms — il vous dit comment trouver les bonnes personnes.
- Meilleure voie d’introduction — la route chaleureuse la plus réaliste pour votre parcours : un employeur, une école ou une communauté commune ; une relation de second degré ; ou un message à froid à fort signal lorsque c’est réellement la meilleure option.
- Brouillons de prise de contact — des messages courts et précis (trois à cinq phrases, sans remplissage) pour vos principales personas, ancrés dans vos preuves réelles pour qu’ils ne paraissent pas génériques.
Enregistrer et revoir des plans
Cliquez sur Enregistrer le plan pour en conserver un. Il est écrit dans la couche utilisateur de votre projet parent, à networking/net-{company}-{role}-{date}.md — votre propre fichier, jamais écrasé par les mises à jour du système. La liste Plans enregistrés en bas de la page vous permet de rouvrir ou de supprimer n’importe quel plan. Comme les brouillons et les personas ne s’appuient que sur vos supports réels, considérez-les comme un solide premier jet à personnaliser — pas comme un script à envoyer à l’aveugle.
24. CV Studio (#/cv-studio)
Ajouter au CV (v1.117.0). Une nouvelle carte transforme un projet, une publication ou une page de portfolio (URL ou texte collé) en puces prêtes pour les ATS fondées UNIQUEMENT sur cette source — les métriques, employeurs ou dates absents sont omis, jamais inventés. Vous relisez les suggestions et collez vous-même celles que vous acceptez dans l’éditeur de CV ; rien n’est écrit automatiquement et les URL passent par le même validateur anti-SSRF que le pipeline.
La page #/cv est l’endroit où vous rédigez votre CV ; le CV Studio (ouvrez-le depuis Setup → CV Studio 🎨 dans la barre latérale) est l’endroit où vous l’affinez. Il offre à votre cv.md trois outils honnêtes, dont deux ne quittent jamais votre navigateur.
Adapter à une offre (v1.101). Collez une offre d’emploi et CV Studio produit un CV adapté plus une lettre de motivation, passés par un contrôle de type recruteur (les erreurs bloquent, les avertissements conseillent), fondé uniquement sur vos matériaux.
Diagnostic du CV
Dès que vous ouvrez la page, il note votre CV sur 100 et liste les constats par vérification, chacun accompagné d’une brève explication pour que vous décidiez de ce qu’il faut changer (il ne réécrit jamais en silence) :
- Longueur — le CV est-il dans une fourchette saine d’une à deux pages ?
- Impact chiffré — quelle part de vos puces comporte un chiffre ou une métrique réels ? Les recruteurs les repèrent en survolant.
- Verbes d’action forts — signale les formulations faibles comme « responsable de » ou « aidé à ».
- Mots à la mode — signale les clichés creux (« orienté résultats », « esprit d’équipe »).
- Sections essentielles — vérifie la présence de Résumé, Expérience, Formation et Compétences.
- Coordonnées — s’assure qu’un e-mail est présent.
Tout cela s’exécute entièrement dans votre navigateur, sans aucun LLM — les chiffres sont déterministes et rien n’est inventé.
Masque de confidentialité
Avant de partager votre CV comme échantillon d’écriture ou capture d’écran, le Masque de confidentialité occulte les données personnelles identifiantes : e-mail, téléphone, liens/identifiants et adresse postale, ainsi que votre nom → initiales si vous l’activez et le saisissez. Activez ou désactivez chaque catégorie, copiez la version masquée et partagez-la en toute sécurité. Tout se passe intégralement dans le navigateur, il indique exactement combien d’éléments ont été occultés et ne stocke ni ne transmet jamais l’original.
Rendez-le humain (correspondance de voix)
Collez une phrase ou un paragraphe rigide — ce genre de formulation générique d’IA qui sonne comme du texte tout fait — et Rendez-le humain le réécrit dans votre voix. La réécriture est ancrée côté serveur dans votre voice-dna.md (la façon dont votre écriture se lit) et vos writing-samples/ (votre prose réelle). La règle stricte : il peut réorganiser, resserrer et réajuster la voix, mais il n’introduira jamais un fait, une métrique ou une réalisation qui ne figure pas déjà dans le texte que vous avez collé. Avec une clé LLM, il réécrit en direct ; sans clé, il vous remet un prompt prêt à coller dans n’importe quel assistant. Modifiez ensuite votre CV sur la page #/cv comme d’habitude — le CV Studio suggère, vous décidez.
25. Mémoire (#/memory)
Toutes les autres pages repartent de zéro à chaque fois. La Mémoire (ouvrez-la depuis Configuration → Mémoire 🧠 dans la barre latérale) est le seul endroit où vous dites quelque chose à l’assistant une seule fois pour que cela reste. Elle contient une note courte et modifiable du type « retiens ceci à mon sujet » qui est injectée dans chaque requête IA.
À quoi elle sert
Utilisez-la pour des préférences durables et un style de travail, par exemple :
- Les types de postes et d’entreprises que vous visez (et ceux que vous ne voulez jamais voir).
- La façon dont vous aimez que les réponses soient rédigées — concises ou détaillées, ton senior, sans remplissage.
- Les contraintes fermes qui méritent d’être répétées — télétravail uniquement, un salaire plancher, pas d’astreinte.
Limitez-la aux préférences et à l’orientation. Ce n’est pas l’endroit pour des faits sur votre expérience — vos compétences, employeurs et réalisations vivent dans votre CV, votre profil et votre synthèse de deux pages, qui restent les seules sources de tout ce qui apparaît dans vos CV et lettres de motivation. La note de mémoire façonne comment l’assistant travaille avec vous, jamais *ce qu’*il affirme à votre sujet.
Comment elle atteint tout
Quand vous cliquez sur Enregistrer la mémoire, la note est écrite dans la couche utilisateur de votre projet parent à config/memory.md et intégrée au contexte partagé du projet. Cela signifie qu’elle voyage automatiquement avec chaque requête IA — évaluations, entretiens simulés, plans de réseautage, réécritures dans CV Studio — et à travers chaque fournisseur que vous avez configuré. Écrivez-la une fois ; vous n’avez pas à vous répéter sur chaque page. Comme vos autres fichiers de la couche utilisateur, elle n’est jamais écrasée quand vous mettez le système à jour, et elle ne quitte votre machine qu’à l’intérieur des prompts LLM que vous choisissez d’exécuter.
Suggérer à partir de vos données
Vous ne savez pas quoi écrire ? ✨ Suggérer à partir de mes données lit votre suivi de candidatures et rédige un ensemble de points comportementaux — les tendances dans ce que vous poursuivez, acceptez et refusez. Exécutez le prompt qu’il vous donne dans n’importe quel LLM, examinez les suggestions et collez une version modifiée dans la note. Il n’exploite que votre propre suivi et n’invente jamais de faits ; vous relisez toujours avant que quoi que ce soit ne soit enregistré.
26. Statistiques (#/stats)
Onglet motifs de rejet (v1.117.0). Un quatrième onglet exécute le analyze-patterns.mjs du parent (lecture seule) et montre la répartition des issues, des recommandations actionnables et le taux d’avancement par fournisseur ATS (le signal de « monoculture algorithmique » — Bommasani et al., FAccT 2026). Les fournisseurs sous l’échantillon minimal portent un astérisque ; sans le projet parent, l’onglet l’indique honnêtement.
La page Statistiques réunit trois vues sous une même section : un rapport de marché généré par l’IA, des analyses sur votre propre pipeline, et la tendance des offres pour vos rôles cibles issue de vos scans. Passez de l’une à l’autre avec les onglets en haut.
Rapport de marché
L’onglet Rapport de marché demande au modèle une analyse des salaires et du marché du travail pour vos rôles cibles — il lit votre CV et votre profil pour savoir quels rôles et quel niveau de séniorité couvrir. Saisissez une Région / marché (par exemple Russia, EU-remote, US ou Germany), choisissez une Devise, puis cliquez sur Générer le rapport de marché. Vous obtenez un rapport structuré avec un résumé exécutif, les salaires par grade (médiane plus P10/P25/P75/P90), les principaux employeurs, un tableau des compétences recherchées, la fréquence des avantages, la répartition présentiel/hybride/télétravail, les tendances sur 12–24 mois y compris l’impact de l’IA, et des conseils de négociation. Chaque chiffre est une estimation indicative issue des connaissances d’entraînement du modèle — ni extraites ni en temps réel — et le rapport le précise ; traitez les nombres comme des fourchettes, pas comme des devis. Sans clé API configurée, vous obtenez à la place un prompt à copier-coller plutôt qu’un rapport fabriqué. Utilisez Télécharger .md, Enregistrer en PDF ou Copier pour sortir le rapport de l’application.
Mon pipeline
L’onglet Mon pipeline trace votre propre suivi de candidatures — rien d’externe. Il montre combien de rôles vous avez suivis, votre distribution de scores, l’entonnoir des statuts, vos principales entreprises et rôles, les candidatures dans le temps, et les taux de conversion (quelle part des candidatures atteint Applied, Responded, Interview et Offer). C’est le miroir honnête de votre recherche : il ne reflète jamais que ce qui figure déjà dans data/applications.md.
Tendance des rôles cibles
L’onglet Tendance des rôles cibles est la vue d’origine : le nombre d’offres et le salaire médian par pays pour vos rôles cibles, agrégés depuis votre dernier scan, avec un sélecteur de devise et un aperçu Offres par rôle cible. Enregistrer un instantané consigne l’agrégat actuel pour que vous puissiez observer l’évolution du nombre d’offres dans le temps, et la courbe de tendance relit ces instantanés. Des données éparses sont attendues et étiquetées comme indicatives — elles ne sont jamais complétées par des nombres inventés.
Cumul et rémunération
L’onglet Cumul (v1.118.0) relaie en lecture seule deux scripts parents à zéro token : stats.mjs — le récapitulatif cumulé de votre tracker, les taux d’entonnoir cumulés (réponse / entretien / offre), les totaux du scanner et la couverture des portails — et salary-gap.mjs — la rémunération souhaitée vs annoncée vs réelle par candidature, consolidée depuis les Machine Summary des rapports et data/salary-observations.tsv. Les petits échantillons sont marqués comme indicatifs ; sans le projet parent, l’onglet affiche une note honnête.
27. Plan de carrière (#/career-plan)
La page Plan de carrière transforme votre CV et votre profil en un plan de développement concret et personnalisé — du genre que vous construiriez avec un coach de carrière, mais généré à partir de vos propres documents et modifiable par vous.
Générer un plan
Choisissez un Horizon (6, 12 ou 24 mois), saisissez éventuellement un Focus (par exemple « passer au management », « passer en télétravail » ou « basculer vers Go »), puis cliquez sur Générer le plan. Le modèle lit votre CV, votre profil, votre two-pager et votre note de mémoire (via le contexte de projet partagé) et rédige un plan structuré : un instantané honnête de votre point de départ, un SWOT des forces et axes de progression, des objectifs exprimés en SMART / OKR / WOOP, des trajectoires de carrière alternatives avec leurs arbitrages, un plan de compétences techniques et comportementales, une feuille de route mois par mois pour l’horizon choisi, comment suivre les progrès, les écueils probables et les actions de soutien. Chaque recommandation s’appuie sur ce que vos documents montrent réellement — le plan se projette vers l’avant, il n’invente jamais de faits sur votre parcours. Sans clé API configurée, vous obtenez à la place une invite à copier-coller.
Modifier et enregistrer
Le plan apparaît dans une zone de texte modifiable — ajustez ce que vous voulez, puis cliquez sur Enregistrer le plan. Il est écrit dans la couche utilisateur de votre projet parent, dans config/career-plan.md, de sorte qu’il survit aux mises à jour du système et n’est envoyé qu’à l’intérieur des invites LLM que vous choisissez d’exécuter. Aperçu rend votre Markdown pour que vous puissiez le lire mis en forme avant de l’enregistrer.
Exporter
Utilisez Télécharger .md, Enregistrer en PDF ou Copier pour sortir le plan de l’application — les mêmes commandes d’export que celles utilisées dans tous les rapports IA de l’application. Le PDF passe par le générateur PDF inline existant ; le Markdown est un téléchargement direct.
28. Orientation de carrière (#/orientation)
La page Orientation de carrière répond à la question « quelles directions me correspondent vraiment ? » — le genre de lecture qu’un test d’orientation vous donnerait, mais déduite de votre propre CV et profil plutôt que d’un questionnaire.
Ce qu’elle produit
Cliquez sur Générer le profil et le modèle lit votre CV, votre profil, votre two-pager et votre note de mémoire et rédige un profil d’orientation de carrière : vos vecteurs de carrière les plus adaptés (lesquels des huit archétypes — Fonctionnaliste, Administrateur, Communicateur, Spécialiste, Analyste, Innovateur, Manager, Entrepreneur — vous correspondent le mieux, avec des preuves tirées de votre CV), une inclinaison de type professionnel, un ensemble de rôles recommandés, vos forces professionnelles liées à ce que montre le CV, des tendances de style de travail (« comment se lit votre CV » sur quelques axes) et des recommandations de développement pour élargir votre adéquation.
Comment elle est générée
C’est une réflexion d’IA sur la façon dont se lit votre CV — pas un test psychométrique. L’invite s’appuie entièrement sur vos propres documents : elle n’invente pas de réalisations et ne rapporte jamais de scores numériques de test comme s’ils étaient mesurés. Sans clé API configurée, vous obtenez une invite à copier-coller à exécuter dans n’importe quel LLM au lieu d’un profil en direct. Rien n’est écrit sur le disque — le profil est généré à neuf à chaque fois.
Exporter
Utilisez Télécharger .md, Enregistrer en PDF ou Copier pour conserver le profil — les mêmes commandes d’export que celles utilisées dans les rapports IA de l’application. Le PDF passe par le générateur PDF inline existant ; le Markdown est un téléchargement direct.