Votre MacBook doit travailler pour vous, et non l’inverse. Il est donc essentiel d’utiliser les capacités dont vous disposez localement en automatisant au plus tôt vos tâches et flux de travail répétitifs.
Ici, nous allons automatiser la génération de lettres de motivation personnalisées que vous envoyez aux entreprises avec votre CV. Comme la lettre est adaptée à une entreprise, un poste et une date précis, copier une lettre, modifier ces champs puis enregistrer en PDF devient long lorsque vous postulez à des dizaines d’offres.
Vue d’ensemble
Nous allons créer un script Bash et un script JavaScript pour personnaliser et générer la lettre de motivation en PDF. Ensuite, nous ajouterons ce script à Raycast pour l’appeler depuis son interface.
Note : Nous sommes obligés d’utiliser le script JS car, en mettant à jour le fichier .docx avec uniquement des outils intégrés, le PDF généré échouait ou supprimait tous les styles du document d’origine.
Prérequis
- Raycast installé.
- NodeJS installé.
- Une lettre de motivation en Word ou Google Docs. Si Google Docs, téléchargez-la en Microsoft Word (.docx) en local.
Installation
Pour tout garder organisé et prêt pour la suite, créons un dossier dans le répertoire utilisateur nommé raycast-scripts/cover-letter, puis allons-y.
cd ~
mkdir raycast-scripts/cover-letter
cd raycast-scripts/cover-letter
Ensuite, installons les paquets nécessaires pour manipuler le fichier docx à l’étape suivante.
npm i pizzip docxtemplater
Création des scripts
Créez deux fichiers : cover-letter-local.sh et generate-cover-letter.mjs. Rendez le script shell exécutable :
chmod +x cover-letter-local.sh
1. cover-letter-local.sh
Ce script est le point d’entrée appelé par Raycast. Il accepte Company (Entreprise) et Role (Poste) en arguments, localise votre modèle DOCX, lance le script Node pour remplir les placeholders, puis utilise Pages (via AppleScript) pour exporter le document rempli en PDF.
#!/bin/bash
# @raycast.schemaVersion 1
# @raycast.title Generate Cover Letter
# @raycast.mode compact
# @raycast.packageName cover-letter
# @raycast.icon 📄
# @raycast.argument1 { "type": "text", "placeholder": "Company (e.g. Acme)" }
# @raycast.argument2 { "type": "text", "placeholder": "Role (e.g. Backend Engineer)" }
set -euo pipefail
COMPANY_NAME="$1"
ROLE_NAME="$2"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
TEMPLATE_DOCX="$SCRIPT_DIR/JobSearch/YOUR_COVER_LETTER_FILE_NAME.docx"
OUTDIR="$HOME/Downloads/Cover Letters"
mkdir -p "$OUTDIR"
NEW_DATE="$(LC_TIME=en_US_POSIX date '+%b %e, %Y' | sed 's/ / /g')"
sanitize() {
echo "$1" \
| tr '\n' ' ' \
| sed -E 's/[\/:*?"<>|]/-/g; s/[[:space:]]+/ /g; s/^[[:space:]]+//; s/[[:space:]]+$//'
}
SAFE_COMPANY="$(sanitize "$COMPANY_NAME")"
BASENAME="YOUR_NAME_${SAFE_COMPANY}_Cover Letter"
TMPDOCX="/tmp/${BASENAME}.docx"
PDFPATH="${OUTDIR}/${BASENAME}.pdf"
if [[ ! -f "$TEMPLATE_DOCX" ]]; then
echo "DOCX template not found: $TEMPLATE_DOCX"
exit 1
fi
if [[ ! -d "$SCRIPT_DIR/node_modules" ]]; then
echo "Run once in this folder: npm install"
exit 1
fi
NODE=""
if command -v node &>/dev/null; then
NODE="node"
elif [[ -x "$HOME/.nvm/current/bin/node" ]]; then
NODE="$HOME/.nvm/current/bin/node"
elif [[ -x /opt/homebrew/bin/node ]]; then
NODE="/opt/homebrew/bin/node"
elif [[ -x /usr/local/bin/node ]]; then
NODE="/usr/local/bin/node"
fi
if [[ -z "$NODE" ]]; then
echo "node not found. Install Node.js or add it to PATH."
exit 1
fi
"$NODE" "$SCRIPT_DIR/generate-cover-letter.mjs" \
"$TEMPLATE_DOCX" "$TMPDOCX" "$COMPANY_NAME" "$ROLE_NAME" "$NEW_DATE"
osascript - "$TMPDOCX" "$PDFPATH" <<'APPLESCRIPT'
on run argv
set docxPosix to item 1 of argv
set pdfPosix to item 2 of argv
set docxAlias to (POSIX file docxPosix) as alias
set pdfFile to POSIX file pdfPosix
tell application "Pages"
activate
open docxAlias
delay 0.8
tell front document
export it to pdfFile as PDF
close saving no
end tell
quit
end tell
end run
APPLESCRIPT
rm -f "$TMPDOCX"
echo "Saved: $PDFPATH"
Ce que fait le script :
- Les métadonnées Raycast (commentaires
@raycast.*) définissent le nom de la commande, l’icône et deux arguments texte pour afficher les champs « Company » et « Role » dans Raycast. - Chemins :
TEMPLATE_DOCXpointe versYOUR_COVER_LETTER_FILE_NAME.docxdans un sous-dossierJobSearch/. RemplacezYOUR_COVER_LETTER_FILE_NAMEpar le nom réel de votre fichier DOCX (sans le chemin). Les PDF sont écrits dans~/Downloads/Cover Letters. - Date : utilise la date du jour au format type
Feb 11, 2025. - Sanitize : nettoie le nom de l’entreprise pour l’utiliser dans le nom de fichier (supprime ou remplace les caractères interdits).
- Nommage :
BASENAMEutiliseYOUR_NAME_pour obtenir des noms du typeYOUR_NAME_Acme_Cover Letter.pdf. RemplacezYOUR_NAME_par votre nom ou vos initiales. - Recherche de Node : essaie
nodedans le PATH, puis les emplacements courants (nvm, Homebrew), pour que le script fonctionne quand il est lancé depuis Raycast (où le PATH peut être minimal). - AppleScript : ouvre le DOCX rempli dans Pages, l’exporte en PDF, ferme le document sans enregistrer puis quitte Pages. Le DOCX temporaire est supprimé.
2. generate-cover-letter.mjs
Ce script Node remplit le modèle DOCX en remplaçant les placeholders texte dans le XML du document. Il n’utilise pas la syntaxe {COMPANY_NAME}, seulement les mots COMPANY_NAME, ROLE_NAME et NEW_DATE dans votre fichier Word.
#!/usr/bin/env node
/**
* Usage: node generate-cover-letter.mjs <template.docx> <output.docx> <company> <role> <date>
*/
import PizZip from "pizzip";
import fs from "fs";
import path from "path";
const [templatePath, outputPath, companyName, roleName, newDate] =
process.argv.slice(2);
const TEMPLATE = templatePath || process.env.TEMPLATE_PATH;
const OUTPUT = outputPath || process.env.OUTPUT_PATH;
const COMPANY = companyName || process.env.COMPANY_NAME || "";
const ROLE = roleName || process.env.ROLE_NAME || "";
const DATE = newDate || process.env.NEW_DATE || "";
if (!TEMPLATE || !OUTPUT) {
console.error(
"Usage: node generate-cover-letter.mjs <template.docx> <output.docx> <company> <role> <date>",
);
process.exit(1);
}
const templateAbs = path.resolve(TEMPLATE);
const outputAbs = path.resolve(OUTPUT);
if (!fs.existsSync(templateAbs)) {
console.error("Template not found:", templateAbs);
process.exit(1);
}
const content = fs.readFileSync(templateAbs, "binary");
const zip = new PizZip(content);
const docPath = "word/document.xml";
const docFile = zip.files[docPath];
if (!docFile) {
console.error("DOCX invalid: missing word/document.xml");
process.exit(1);
}
let xml = docFile.asText();
const replacements = [
["COMPANY_NAME", COMPANY],
["ROLE_NAME", ROLE],
["NEW_DATE", DATE],
];
for (const [placeholder, value] of replacements) {
const escaped = String(value)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
xml = xml.split(placeholder).join(escaped);
}
zip.file(docPath, xml);
const buf = zip.generate({ type: "nodebuffer" });
fs.writeFileSync(outputAbs, buf);
console.log(outputAbs);
Ce qu’il fait :
- Lit le DOCX comme une archive ZIP, modifie
word/document.xmlet écrit un nouveau DOCX. Toute la mise en forme (styles, polices, mise en page) est conservée car seuls les nœuds texte sont modifiés. - Remplace COMPANY_NAME, ROLE_NAME et NEW_DATE par les valeurs passées (ou via les variables d’environnement). Les valeurs sont échappées pour le XML pour que des caractères comme
&ou<ne corrompent pas le fichier.
3. Préparer votre modèle DOCX
- Rédigez votre lettre de motivation dans Word (ou Google Docs, puis Fichier → Télécharger → Microsoft Word (.docx)).
- Là où vous voulez le nom de l’entreprise, tapez COMPANY_NAME (sans accolades).
- Là où vous voulez le poste, tapez ROLE_NAME.
- Là où vous voulez la date, tapez NEW_DATE.
- Enregistrez le fichier et placez-le où
cover-letter-local.shl’attend, par exempleraycast-scripts/cover-letter/JobSearch/YOUR_COVER_LETTER_FILE_NAME.docx. Dans le script, remplacezYOUR_COVER_LETTER_FILE_NAMEpar le nom réel de votre fichier DOCX etYOUR_NAME_par votre nom (ou initiales) pour que les fichiers exportés aient le nom souhaité.
4. Ajouter le script à Raycast
- Ouvrez Raycast → Settings → Extensions.
- Cliquez sur Add Script Directory et choisissez
~/raycast-scripts/cover-letter(ou le dossier contenantcover-letter-local.shetgenerate-cover-letter.mjs). - Raycast détectera le script grâce aux commentaires
@raycast.*. Vous devriez voir Generate Cover Letter sous le package cover-letter. - Lancez-le : saisissez l’entreprise et le poste, puis Entrée. Le script génère le DOCX rempli, l’exporte en PDF via Pages et l’enregistre dans
~/Downloads/Cover Letters.
Vous avez maintenant un flux en une étape : Entreprise + Poste dans Raycast → lettre de motivation PDF personnalisée sur votre Mac, sans recherche-remplacement ni export manuel.
Bonne chance pour décrocher le poste !