# MeetingNotes pour macOS

Application Swift native, sans compte, dans la barre des menus. Elle capture le son système et le microphone avec ScreenCaptureKit, produit un WAV mono 16 kHz, transcrit la réunion, puis rédige un compte rendu Markdown en français. Aucun pilote virtuel, stockage cloud ni télémétrie dans l’application. macOS **15 ou ultérieur**, Apple Silicon ou Intel ; compilation native pour l’architecture du Mac utilisé.

## Installation

Installez les outils Apple récents avec Swift 6 et le SDK macOS 15 ou ultérieur (`xcode-select --install`) et [Homebrew](https://brew.sh), puis :

```sh
brew install ffmpeg whisper-cpp ollama
mkdir -p ~/Models/whisper
curl -L --fail https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.bin \
  -o ~/Models/whisper/ggml-medium.bin
```

Le modèle demandé est **medium multilingue**, pas medium.en. Prévoir environ 1,5 Go pour ce fichier et plusieurs Go supplémentaires pour le LLM et sa mémoire de travail. Pour compiler whisper.cpp depuis les sources à la place du paquet Homebrew :

```sh
brew install cmake
git clone https://github.com/ggml-org/whisper.cpp.git
cd whisper.cpp
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -j
bash models/download-ggml-model.sh medium
```

Dans ce cas, renseignez les chemins absolus de `build/bin/whisper-cli` et `models/ggml-medium.bin` dans `.env`.

### Résumé entièrement local

Lancez Ollama avec le cloud désactivé ; laissez ce terminal ouvert :

```sh
OLLAMA_NO_CLOUD=1 ollama serve
```

Dans un autre terminal :

```sh
ollama pull qwen3:8b
```

Si Ollama est déjà lancé comme application ou service, arrêtez cette instance avant de lancer celle-ci. L’application de notes contacte exclusivement `127.0.0.1:11434` pour le résumé local ; Ollama doit être configuré sans fonctions cloud. Ne choisissez pas de modèle cloud. Le téléchargement initial des outils et modèles nécessite Internet ; une fois présents, transcription et résumé fonctionnent sans connexion. Whisper seul ne suffit pas pour le résumé : **un LLM local et Ollama sont également nécessaires**.

### Construire l’application

Depuis ce dossier :

```sh
./scripts/build.sh
mkdir -p ~/Applications
cp -R dist/MeetingNotes.app ~/Applications/
open ~/Applications/MeetingNotes.app
```

Le résultat est un bundle `.app` signé localement (signature ad hoc). Aucun abonnement Apple ni Xcode complet n’est nécessaire. Pour une signature personnelle stable, utilisez `SIGNING_IDENTITY="Nom de votre identité" ./scripts/build.sh`. Le bundle n’est pas notarié pour distribution à d’autres utilisateurs. Conservez le même emplacement ; recompilation ou changement de signature peut nécessiter de réaccorder les autorisations macOS.

## Autorisations

Au premier clic sur **Démarrer l’enregistrement** :

1. Autorisez **Microphone**.
2. Autorisez **Enregistrement de l’écran et de l’audio système** (le libellé varie suivant macOS) pour MeetingNotes dans Réglages Système → Confidentialité et sécurité.
3. Si macOS le demande, quittez puis rouvrez MeetingNotes et relancez l’enregistrement.

ScreenCaptureKit utilise un écran comme source de capture du son système. L’application ne reçoit ni ne stocke de vidéo ou de capture d’écran. Un écran doit être disponible. Le micro par défaut au début de la session est utilisé. Les changements de périphérique et les mises en veille peuvent interrompre une capture : la session reçue reste récupérable. Aucune permission Accessibilité ni Accès complet au disque n’est requise. Les boîtes de dialogue système doivent être validées manuellement.

## Configuration `.env`

Au premier lancement, l’application crée :

```text
~/Library/Application Support/MeetingNotes/.env
```

Ouvrez-le via **Configurer (.env)**. `.env.example` documente toutes les variables. Le fichier est relu au début de chaque session et à chaque reprise, sans modifier une capture en cours. Les valeurs peuvent être entourées de guillemets ; pas d’expansion de variables shell ni de commentaires en fin de ligne. Les chemins acceptent `~`. Une application lancée depuis Finder n’hérite pas du PATH de votre terminal : les exécutables sont recherchés dans `/opt/homebrew/bin`, `/usr/local/bin`, `/usr/bin`, ou aux chemins explicites configurés.

**Par défaut, `OFFLINE=true` bloque les appels API, même si des clés existent.** Pour activer les API avec sélection automatique :

```dotenv
OFFLINE=false
TRANSCRIPTION_PROVIDER=auto
SUMMARY_PROVIDER=auto
OPENAI_API_KEY=votre-cle
# Ou GROQ_API_KEY=votre-cle
```

`auto` choisit Groq si sa clé existe, sinon OpenAI, sinon le traitement local. Chaque fournisseur peut être fixé indépendamment à `local`, `openai` ou `groq`. Exemple : `TRANSCRIPTION_PROVIDER=local` et `SUMMARY_PROVIDER=openai` envoie seulement la transcription au service de résumé. Le menu indique les fournisseurs choisis. Aucun basculement distant n’est effectué après une erreur locale.

En mode API, l’audio est envoyé au fournisseur de transcription et le texte au fournisseur de résumé. Leur traitement et leur éventuelle conservation relèvent de leurs conditions ; ce mode n’est donc pas entièrement local. Il n’existe aucun autre envoi par l’application. Les clés ne sont ni incluses dans le bundle, ni affichées dans les erreurs, ni enregistrées dans les notes. Le fichier `.env` créé par l’app est privé (0600).

Les modèles distants sont configurables : `OPENAI_LLM_MODEL`, `GROQ_LLM_MODEL`, `GROQ_WHISPER_MODEL`. Les noms et accès peuvent changer chez les fournisseurs. `LANGUAGE=auto` laisse Whisper détecter la langue ; `fr` peut améliorer les réunions françaises. Les erreurs HTTP ne déclenchent pas de répétition automatique pouvant multiplier les frais.

## Utilisation et fichiers

Cliquez sur l’icône onde sonore → **Démarrer l’enregistrement**. Un indicateur rouge et la durée restent visibles. **Arrêter et créer la note** sauvegarde, transcrit et résume en arrière-plan ; le menu affiche la progression puis ouvre le Markdown.

```text
~/MeetingNotes/2026-09-28-0930.md
~/MeetingNotes/2026-09-28-0930.wav
~/MeetingNotes/2026-09-28-0930.session/transcript.txt
```

Le Markdown contient le résumé en cinq points, les décisions et les actions avec propriétaires et échéances, puis un séparateur et la transcription complète. Les propriétaires absents sont signalés « Non précisé ». Whisper n’identifie pas les locuteurs : le modèle ne doit attribuer une action que si le texte nomme explicitement son propriétaire. Vérifiez les attributions et décisions avant utilisation.

Les sessions débutant la même minute reçoivent un suffixe `-2`, `-3`, etc. pour éviter l’écrasement. Le dossier `.session` contient d’abord les pistes `system.caf` et `microphone.caf` ; elles sont supprimées après création réussie du WAV. Il conserve la transcription utile à la reprise. Prévoyez plusieurs Go libres pendant une longue capture : les pistes temporaires sont non compressées. Le WAV final occupe environ 115 Mo par heure. Les pistes sont alignées sur l’horloge système, avec silence ajouté pour les interruptions. Le WAV est un mélange des deux sources, pas une diarisation. Un casque réduit l’écho de la voix des participants captée aussi par votre micro. Tout le son système autorisé est inclus, notamment notifications et autres applications.

Les appels de transcription API utilisent des segments de dix minutes (moins de 20 Mo chacun). Une frontière de segment peut couper un mot. Les longues transcriptions sont résumées par passages puis consolidées ; la transcription intégrale est conservée, mais la consolidation peut perdre des détails. Le respect exact de cinq points dépend aussi du modèle utilisé.

## Erreurs et reprise

Le WAV est conservé même si Whisper ou le LLM échoue. La transcription est écrite avant le résumé : en cas de panne du LLM, elle figure déjà dans le Markdown avec le statut « Résumé en attente ».

Corrigez `.env`, démarrez Ollama ou installez les dépendances manquantes, puis choisissez **Reprendre une session…** et sélectionnez le dossier `.session` ou le `.wav`. Une transcription déjà réussie est réutilisée ; une transcription API interrompue repart du début. Reprendre une session terminée régénère son résumé dans le même Markdown : sauvegardez vos modifications manuelles avant cette action.

En cas d’arrêt brutal, les pistes CAF déjà écrites restent dans `.session` ; la reprise tente de les convertir. Une panne disque ou un fichier tronqué peut empêcher la récupération. Aucun mécanisme ne peut garantir la récupération des derniers buffers non écrits. Le traitement ne bloque pas le menu ; quitter est désactivé pendant la capture et le traitement pour éviter une interruption accidentelle. Un processus externe a un délai maximal de deux heures, une requête API d’une heure. Il n’y a pas de bouton d’annulation forcée.

- **Aucun son système** : vérifiez l’autorisation, puis relancez l’application. Certains contenus protégés ne peuvent pas être capturés.
- **Aucun micro** : vérifiez l’entrée sonore macOS et son autorisation ; faites une courte réunion de test.
- **Erreur locale de connexion** : lancez Ollama avec `OLLAMA_NO_CLOUD=1`, vérifiez que le modèle est téléchargé.
- **Modèle introuvable** : renseignez `WHISPER_MODEL` et `WHISPER_BIN` ; les fichiers medium et medium.en sont différents.
- **Notes confidentielles** : elles sont dans votre dossier personnel, sous les permissions de votre compte. L’app n’ajoute pas de chiffrement ; FileVault et vos sauvegardes restent gérés par macOS. Évitez de placer ce dossier sous une synchronisation cloud si vous souhaitez une conservation strictement locale.

## Vérification

```sh
./scripts/test.sh
./scripts/build.sh
```

Les tests Swift Testing couvrent aussi le mixage de deux pistes synthétiques et la conservation des données quand le résumé échoue. Ils couvrent la priorité du mode hors ligne, la lecture `.env`, le choix du fournisseur, les collisions de noms, le découpage Unicode et la persistance de la transcription. La capture réelle nécessite les autorisations interactives macOS. Test manuel recommandé : enregistrez 20 secondes de parole au micro pendant la lecture d’un son dans une autre application, arrêtez, écoutez le WAV et contrôlez la note. Répétez sans réseau avec les deux modèles locaux installés. Testez aussi un résumé avec Ollama arrêté, puis la reprise après redémarrage du service.

## Références

- [Capture du microphone dans ScreenCaptureKit](https://developer.apple.com/documentation/screencapturekit/scstreamconfiguration/capturemicrophone)
- [whisper.cpp et modèles](https://github.com/ggml-org/whisper.cpp)
- [API de transcription OpenAI](https://developers.openai.com/api/docs/guides/speech-to-text)
- [API locale Ollama](https://github.com/ollama/ollama/blob/main/docs/api.md)

- [Désactiver le cloud Ollama](https://docs.ollama.com/faq#how-do-i-disable-ollamas-cloud-features)
- [Transcription Groq](https://console.groq.com/docs/speech-to-text)
