Agency Docs Updater
Exécutez TOUTES les étapes automatiquement en séquence. Ne vous arrêtez que si une étape échoue et ne peut pas être récupérée. Lisez references/learnings.md avant de commencer pour les pièges connus.
Configuration : les chemins sont lus depuis .env à la racine du skill (voir .env.example). Les valeurs par défaut fonctionnent pour la configuration standard. Variables d'environnement clés : VAULT_DIR, DOCS_SITE_DIR, YOUTUBE_UPLOADER_DIR, PRESENTATIONS_DIR, SKILLS_REPO_DIR, SKILLS_LOCAL_DIR, ZOOM_CREDENTIALS_DIR, GITHUB_REPO, SITE_DOMAIN.
Dépendances (vérifiez qu'elles existent avant d'exécuter) :
- zoom — Téléchargement des enregistrements Zoom (
scripts/zoom_meetings.py) - fathom — Solution de repli vidéo Fathom (
scripts/download_video.py) - nano-banana — Génération de superposition de vignettes (
scripts/generate_image.sh) - calendar-sync — local uniquement, synchronisation des événements du calendrier (
sync.sh) - youtube-uploader — Traitement vidéo, upload et authentification API YouTube
Étape 0 : Analyser les arguments et charger la configuration
Chargez .env depuis la racine du skill. Ensuite, séparez args par espaces :
- Token à 8 chiffres (
YYYYMMDD) →DATE - "yesterday" →
DATE = $(date -v-1d +%Y%m%d) - "today" ou absent →
DATE = $(date +%Y%m%d) - Token à 2 chiffres (
NN) oulab-NN→LAB_FILTER
Développez les variables d'environnement pour les chemins utilisés dans les étapes suivantes :
VAULT_DIR="${VAULT_DIR:-$HOME/Brains/brain}"
DOCS_SITE_DIR="${DOCS_SITE_DIR:-$HOME/Sites/agency-docs}"
YOUTUBE_UPLOADER_DIR="${YOUTUBE_UPLOADER_DIR:-$HOME/ai_projects/youtube-uploader}"
SKILLS_REPO_DIR="${SKILLS_REPO_DIR:-$HOME/ai_projects/claude-skills}"
SKILLS_LOCAL_DIR="${SKILLS_LOCAL_DIR:-$HOME/.claude/skills}"
ZOOM_CREDENTIALS_DIR="${ZOOM_CREDENTIALS_DIR:-$HOME/.zoom_credentials}"
PRESENTATIONS_DIR="${PRESENTATIONS_DIR:-$HOME/ai_projects/claude-code-lab}"
GITHUB_REPO="${GITHUB_REPO:-glebis/agency-docs}"
SITE_DOMAIN="${SITE_DOMAIN:-agency-lab.glebkalinin.com}"
Étape 1 : Trouver la transcription Fathom
Si LAB_FILTER est défini : ${VAULT_DIR}/${DATE}-claude-code-lab-${LAB_FILTER}.md
Si vide : glob ${VAULT_DIR}/${DATE}-claude-code-lab-*.md (choisir le plus récent par date de modification).
Si manquant : exécuter ${SKILLS_LOCAL_DIR}/calendar-sync/sync.sh, revérifier, s'arrêter si toujours manquant.
Extraire du frontmatter YAML et stocker :
FATHOM_FILE,SHARE_URL,MEETING_TITLE,DATE,LAB_NUMBERVIDEO_NAME=${DATE}-claude-code-lab-${LAB_NUMBER}TRANSCRIPT_LANG= détection automatique à partir des ~50 premières lignes (ratio cyrillique > 0,3 →ru, sinonen)
Déterminer MEETING_NUMBER : vérifier les fichiers MDX existants dans ${DOCS_SITE_DIR}/content/docs/claude-code-internal-${LAB_NUMBER}/meetings/ pour un espace réservé avec la date d'aujourd'hui. S'il est trouvé, utiliser ce numéro. Sinon, vérifier les tailles de contenu des fichiers pour trouver le prochain emplacement vide. Stocker sous forme de chaîne à deux chiffres avec zéro non significatif (par exemple 04). Cette variable est utilisée dans les étapes 3b, 4b, 5, 6 et 8.
Étape 2 : Télécharger la vidéo
Ignorer si ${VAULT_DIR}/${VIDEO_NAME}.mp4 existe et fait plus de 1 Mo.
Remarque : les enregistrements Zoom peuvent prendre environ 15 minutes à traiter après la fin d'une réunion. Si l'API Zoom ne renvoie aucun enregistrement, attendez et réessayez avant de recourir à Fathom.
Principal — Zoom :
python3 ${SKILLS_REPO_DIR}/zoom/scripts/zoom_meetings.py recordings \
--start ${DATE:0:4}-${DATE:4:2}-${DATE:6:2} \
--end $(date -j -v+1d -f %Y%m%d ${DATE} +%Y-%m-%d) \
--show-downloads 2>&1
Trouvez l'URL MP4, puis :
TOK=$(python3 -c "import json,pathlib; print(json.load(open(pathlib.Path('${ZOOM_CREDENTIALS_DIR}')/'oauth_token.json'))['access_token'])")
curl -L -H "Authorization: Bearer ${TOK}" -o ${VAULT_DIR}/${VIDEO_NAME}.mp4 "${MP4_DOWNLOAD_URL}"
Solution de repli — Fathom (si aucun enregistrement Zoom) :
cd ${VAULT_DIR} && python3 ${SKILLS_LOCAL_DIR}/fathom/scripts/download_video.py \
"${SHARE_URL}" --output-name "${VIDEO_NAME}"
Étape 3 : Uploader sur YouTube
cd ${YOUTUBE_UPLOADER_DIR} && \
python3 process_video.py \
--video ${VAULT_DIR}/${VIDEO_NAME}.mp4 \
--fathom-transcript ${FATHOM_FILE} \
--title "${MEETING_TITLE}" \
--upload
Exécuter avec run_in_background: true (10-30 min). En cas d'échec : --resume-from upload.
Extraire YOUTUBE_URL de la sortie standard (✓ YouTube video: ...) ou de processed/metadata/${VIDEO_NAME}.json.
Extraire VIDEO_ID de l'URL (la partie après ?v= ou le dernier segment du chemin).
Étape 3a : Vérifier l'upload (OBLIGATOIRE)
Après avoir extrait VIDEO_ID, vérifiez que la vidéo existe réellement sur YouTube avant de continuer. Les vidéos peuvent échouer silencieusement lors du traitement ou être automatiquement supprimées par l'examen de contenu de YouTube.
cd ${YOUTUBE_UPLOADER_DIR} && PYTHONPATH=. python3 -c "
from auth import get_authenticated_service
import sys, time
youtube = get_authenticated_service()
video_id = '${VIDEO_ID}'
# Interroger jusqu'à 5 minutes pour que la vidéo devienne disponible
for attempt in range(10):
resp = youtube.videos().list(part='status,processingDetails', id=video_id).execute()
if not resp['items']:
if attempt < 9:
print(f'Vidéo pas encore disponible (tentative {attempt+1}/10), attente de 30s...')
time.sleep(30)
continue
print(f'ERREUR FATALE : Vidéo {video_id} introuvable après 5 minutes. L\'upload a peut-être échoué.')
sys.exit(1)
status = resp['items'][0]['status']
processing = resp['items'][0].get('processingDetails', {})
upload_status = status.get('uploadStatus', 'inconnu')
privacy = status.get('privacyStatus', 'inconnu')
rejection = status.get('rejectionReason', None)
print(f'Statut upload : {upload_status}, Confidentialité : {privacy}')
if rejection:
print(f'REJETÉ : {rejection}')
sys.exit(1)
if upload_status in ('processed', 'uploaded'):
print(f'✓ Vidéo {video_id} vérifiée OK')
sys.exit(0)
if upload_status == 'failed':
print(f'ERREUR FATALE : Échec de l\'upload — {status.get(\"failureReason\", \"inconnu\")}')
sys.exit(1)
print(f'Statut : {upload_status}, attente de 30s...')
time.sleep(30)
print('ERREUR FATALE : Vidéo pas prête après 5 minutes')
sys.exit(1)
"
Si la vérification échoue : supprimez les métadonnées de la vidéo échouée (rm processed/metadata/${VIDEO_NAME}.json), ré-uploader avec --resume-from upload, et revérifier. Ne PAS passer aux étapes MDX ou de vignette avec un VIDEO_ID non vérifié.
Démarrer l'Étape 4 en parallèle — le résumé ne dépend pas de l'URL YouTube.
Étape 3b : Vignette de style Lab (OBLIGATOIRE)
Exécutez toujours cette étape — elle remplace la vignette générique de process_video.py par le modèle de lab personnalisé. La vignette générique n'est PAS acceptable pour la publication.
Prérequis : VIDEO_ID doit être connu (attendez la fin de l'Étape 3 si nécessaire).
Suivez references/thumbnail-guide.md pour le flux de travail complet :
- Générer l'image de superposition Nano Banana (invite spécifique au sujet à partir des modèles d'invite du guide)
- Lire/inspecter l'image brute pour confirmer la couleur de fond, puis recolorier les lignes en orange (#e85d04)
- Écrire un fichier HTML temporaire (par exemple
/tmp/lab-meeting-${MEETING_NUMBER}.html) basé sur${YOUTUBE_UPLOADER_DIR}/templates/images/lab-meeting.html— mettre à jour le numéro de réunion, le texte hero du sujet, les descriptions en points, la date. Ne modifiez pas le modèle original sur place. - Rendre avec Playwright en 1280×720 →
${YOUTUBE_UPLOADER_DIR}/processed/thumbnails/${VIDEO_NAME}.jpg - Lire/inspecter la vignette rendue pour vérifier la mise en page avant de la télécharger
- Uploader sur YouTube : utiliser
VIDEO_IDextrait de l'Étape 3
Ne PAS sauter cette étape ni compter sur la vignette de process_video.py.
Étape 4 : Générer un résumé vérifié
Lisez ${FATHOM_FILE}. Générez un résumé structuré dans ${TRANSCRIPT_LANG} :
- En-têtes de section
##, points de liste, exemples de code le cas échéant - Termes techniques en anglais (MCP, Skills, Claude Code, etc.)
- Exclure les détails personnels de planification
- Nettoyer pour MDX : échapper les caractères
<,>et les{nus qui casseraient la compilation MDX
Vérifiez les affirmations sur les fonctionnalités de Claude Code à l'aide du sous-agent claude-code-guide (si disponible ; sautez la vérification si l'agent n'est pas accessible). Enregistrez le résumé corrigé dans le bloc-notes comme summary.md.
Étape 4b : Mettre à jour les métadonnées YouTube
Après la fin des Étape 3 et Étape 4. VIDEO_ID, MEETING_NUMBER et LAB_NUMBER doivent tous être déterminés avant cette étape. Lisez references/youtube-api.md pour le format de description et les extraits d'API.
Générez la description YouTube à partir du résumé. Utilisez le modèle correspondant à la langue :
- Si
TRANSCRIPT_LANG=en: libellés en anglais ("In this video:", "Course materials and session notes:") - Si
TRANSCRIPT_LANG=ru: libellés en russe ("В этом видео:", "Материалы и конспект занятия:")
Ne mélangez PAS les langues dans une seule description.
URL de la page de réunion : https://${SITE_DOMAIN}/claude-code-lab-${LAB_NUMBER}/meetings/${MEETING_NUMBER}
Mettez à jour le titre, la description, les balises via l'API YouTube, puis ajoutez la vidéo à la playlist "Claude Code Lab ${LAB_NUMBER}" (créée automatiquement si elle n'existe pas).
Étape 5 : Générer MDX
python3 ${SKILLS_LOCAL_DIR}/agency-docs-updater/scripts/update_meeting_doc.py \
${FATHOM_FILE} "${YOUTUBE_URL}" ${SCRATCHPAD}/summary.md
Avant d'exécuter : vérifiez si un MDX d'espace réservé existe déjà pour la date d'aujourd'hui (grep -l dans meetings/). Si c'est le cas, utilisez -n ${MEETING_NUMBER} --update pour le cibler.
Après avoir exécuté :
- Supprimez le contenu Marp ajouté (tout après le
---de fermeture du résumé avant<!-- _class: lead -->) — MDX se casse sur les commentaires HTML (<!-- -->), les<non échappés et les caractères{nus - Vérifiez la présence d'un fichier de présentation : cherchez dans
${PRESENTATIONS_DIR}/presentations/lab-${LAB_NUMBER}/et${PRESENTATIONS_DIR}/lesson-generator/pour les fichiers correspondant à${DATE}. S'il est trouvé, copiez-le dans${DOCS_SITE_DIR}/public/${DATE}-claude-code-lab-${LAB_NUMBER}.htmlet ajoutez le lien dans MDX - Remplacez les espaces réservés du frontmatter (
[Название встречи],[Краткое описание встречи],[Дата встречи]) - Si
TRANSCRIPT_LANG=en, réécrivez entièrement le MDX avec des libellés en anglais — le script utilise le russe par défaut et le repli de traduction produit une sortie en langage mixte cassée - Vérifiez :
cd ${DOCS_SITE_DIR} && npm run build 2>&1 | tail -5
Étape 6 : Commiter et pousser
Ne staging que les fichiers du pipeline — jamais git add . :
cd ${DOCS_SITE_DIR}
git fetch origin main
BEHIND=$(git rev-list --count HEAD..origin/main)
if [ "$BEHIND" -gt 0 ]; then
git stash push -m "agency-docs-updater: temp stash"
git pull --rebase origin main
git stash pop || true
fi
git add content/docs/claude-code-internal-${LAB_NUMBER}/meetings/${MEETING_NUMBER}.mdx
# Ne staging que le HTML de la présentation s'il a été copié
[ -f public/${DATE}-claude-code-lab-${LAB_NUMBER}.html ] && git add public/${DATE}-claude-code-lab-${LAB_NUMBER}.html
git commit -m "Ajouter la réunion ${MEETING_NUMBER} du Lab ${LAB_NUMBER}"
git push
Stockez COMMIT_HASH=$(git rev-parse HEAD) pour l'Étape 7.
Étape 7 : Attendre le déploiement Vercel
TIMEOUT=300; ELAPSED=0
until [ "$(gh api repos/${GITHUB_REPO}/commits/${COMMIT_HASH}/status --jq '.state' 2>/dev/null || echo 'pending')" != "pending" ]; do
sleep 15; ELAPSED=$((ELAPSED+15))
[ "$ELAPSED" -ge "$TIMEOUT" ] && echo "Délai de déploiement dépassé après ${TIMEOUT}s" && break
done
DEPLOY_STATE=$(gh api repos/${GITHUB_REPO}/commits/${COMMIT_HASH}/status --jq '.state')
echo "État du déploiement : ${DEPLOY_STATE}"
Exécuter avec run_in_background: true. Si l'état est failure ou error : vérifiez les logs Vercel (vercel logs), corrigez localement, repoussez, redémarrez cette étape.
Étape 8 : Vérifier dans le navigateur
Ouvrez https://${SITE_DOMAIN}/claude-code-lab-${LAB_NUMBER}/meetings/${MEETING_NUMBER} dans un navigateur (via des outils d'automatisation Chrome ou manuellement). Vérifiez que l'embed YouTube est visible. Sinon : vérifiez VIDEO_ID, attendez le traitement YouTube, ou rechargez.
Rapport du pipeline
Après la fin, rapportez : chemin Fathom, chemin vidéo, URL YouTube, chemin MDX, hash du commit, état du déploiement, vérification de l'embed.