3
This commit is contained in:
@@ -0,0 +1,624 @@
|
||||
# **Architecture logicielle et spécifications techniques pour une suite de productivité personnelle autohébergée**
|
||||
|
||||
La mise en œuvre d'une suite de productivité personnelle autohébergée — unifiant la gestion de tâches, de calendriers multi-sources, de listes de courses adaptées au format mobile et de tableaux Kanban — nécessite un examen rigoureux des architectures de communication et des protocoles de synchronisation. Ce rapport détaille le processus de brainstorming technique, la structure de l'API passerelle, les spécifications de l'interface utilisateur multi-plateforme, le moteur de gestion des calendriers et les interfaces d'intelligence artificielle requises pour ce projet.
|
||||
|
||||
## **Brainstorming technique et sélection des composants logiciels**
|
||||
|
||||
Pour répondre aux exigences de flexibilité et de performance, le choix entre une architecture centralisée autour d'un système de gestion de base de données (BaaS) et l'assemblage de briques logicielles préexistantes a été évalué. Le tableau ci-dessous présente une analyse comparative des technologies étudiées pour structurer les différents modules de l'application.
|
||||
|
||||
| Composant logiciel | Rôle fonctionnel | Avantages majeurs | Inconvénients et contraintes techniques |
|
||||
| :---- | :---- | :---- | :---- |
|
||||
| **Vikunja API** 1 | Gestion des tâches (todos) et tableaux Kanban 2 | API REST Swagger native \[/api/v1/docs\]2, gestion granulaire des filtres de recherche et des erreurs 2, support natif du Kanban.2 | Structure Go/TypeScript monolithique 4 exigeant une couche d'adaptation pour s'intégrer à un frontend SvelteKit sur mesure. |
|
||||
| **PocketBase** 5 | Backend général, stockage de la liste de courses et des notes d'idées | Binaire Go unique, base de données SQLite embarquée, support natif des abonnements temps réel via Server-Sent Events (SSE) et gestion native des utilisateurs.5 | Absence de logique métier par défaut pour la réconciliation complexe d'événements de calendrier ou la modélisation Kanban évoluée. |
|
||||
| **Keeper.sh** 8 | Moteur de synchronisation bidirectionnelle de calendriers | Licence open-source AGPL-3.0, agrégation de flux multiples, réconciliation automatique des conflits, serveur MCP intégré.8 | Nécessite des instances PostgreSQL et Redis pour l'orchestration des files d'attente, ce qui augmente l'empreinte mémoire.9 |
|
||||
| **SvelteKit avec Vite-PWA** 11 | Framework applicatif du client unique (PWA) | Précaching par Workbox, gestion robuste du mode hors-ligne 11, compilation d'actifs ultra-légers pour ordinateurs tactiles et mobiles. | Exige une gestion stricte des restrictions d'arrière-plan imposées par le moteur WebKit sur iOS.13 |
|
||||
| **Radicale / Baïkal** 15 | Serveur CalDAV / CardDAV de stockage local | Protocoles standards, légèreté extrême, intégration native avec les clients iOS et Android.15 | Ne propose aucun mécanisme de synchronisation active ou de réconciliation automatique vers des fournisseurs tiers comme Google.16 |
|
||||
| **Vdirsyncer** 17 | Outil CLI de synchronisation de répertoires DAV | Synchronisation bidirectionnelle robuste 18, support natif de Google Calendar.17 | Processus synchrone complexe en ligne de commande, difficile à intégrer dynamiquement dans un backend d'application web.19 |
|
||||
|
||||
L'analyse de ces technologies oriente la conception vers une architecture découplée. Le stockage principal des tâches et des tableaux Kanban est délégué à l'API de Vikunja 2, tandis que la liste de courses personnalisée et le module de notes sont hébergés sur PocketBase pour bénéficier d'une réactivité en temps réel optimale lors de l'usage en grande surface.5 La couche de synchronisation des calendriers s'appuie sur le moteur d'agrégation de Keeper.sh 8, et le frontend SvelteKit unifie l'expérience utilisateur.11
|
||||
|
||||
## **Architecture globale du système et passerelle d'API**
|
||||
|
||||
Pour garantir l'interrogabilité de l'infrastructure par des applications tierces, le système est orchestré autour d'une passerelle d'API (API Gateway) unifiée. Cette passerelle assure le routage des requêtes, la validation de la sécurité et la transformation des charges utiles des protocoles externes, notamment les webhooks Gitea 20 et les requêtes des agents d'intelligence artificielle.21
|
||||
|
||||
\+---------------------------------------+
|
||||
| Application Cliente |
|
||||
| (PWA SvelteKit / Laptop Web) |
|
||||
\+-------------------+-------------------+
|
||||
| HTTPS (REST & SSE)
|
||||
v
|
||||
\+---------------------------------------+
|
||||
| Proxy Inverse (Caddy) |
|
||||
\+-------------------+-------------------+
|
||||
|
|
||||
\+----------------------------+----------------------------+
|
||||
| /api/v1/tasks | /api/v1/courses / notes | /api/v1/calendars
|
||||
v v v
|
||||
\+------------------+ \+------------------+ \+------------------+
|
||||
| Vikunja API | | PocketBase | | Keeper.sh |
|
||||
| (Tâches, Kanban) | | (Courses, Notes) | | (Calendriers) |
|
||||
\+--------+---------+ \+--------+---------+ \+--------+---------+
|
||||
| | |
|
||||
| SQLite / Postgres | SQLite (Multi-Bases) | Postgres / Redis
|
||||
v v v
|
||||
\+----------------------------------------------------------------------------+
|
||||
| Espace de Stockage Persistant |
|
||||
\+----------------------------------------------------------------------------+
|
||||
|
||||
### **Mécanisme d'intégration Gitea**
|
||||
|
||||
La connexion avec le serveur Gitea s'établit par le biais de webhooks sortants déclenchés lors de l'activité des dépôts (création d'un ticket, fermeture d'une pull request ou mise à jour d'un jalon).20 Lorsqu'un événement survient, Gitea émet une requête HTTP POST vers l'endpoint /api/v1/gitea/webhook de la passerelle.20
|
||||
Pour garantir la sécurité de cette intégration, chaque webhook est authentifié à l'aide d'un secret partagé.20 La passerelle valide l'en-tête X-Gitea-Signature en calculant le code HMAC-SHA256 du corps brut de la requête 20 :
|
||||
![][image1]
|
||||
La fonction de validation compare les hachages en temps constant pour faire échec aux attaques par canal auxiliaire. Dès validation, la passerelle convertit la charge utile de Gitea (par exemple, la création d'un ticket doté d'une date d'échéance) 20 en un appel d'API vers Vikunja pour ajouter la tâche dans la liste correspondante 3, puis met à jour l'agenda par l'intermédiaire de l'API de Keeper.sh.9
|
||||
|
||||
### **Compatibilité des calendriers mobiles (iOS et Android)**
|
||||
|
||||
Pour s'assurer que les modifications apportées aux calendriers soient immédiatement visibles sur les terminaux iOS et Android, l'application propose deux canaux de distribution de données synchronisées :
|
||||
|
||||
* **Canal iOS natif :** Les comptes Apple iCloud utilisent le protocole CalDAV standardisé.16 Le système configure un point de terminaison CalDAV local (via l'intégration de Keeper.sh ou un serveur de synchronisation sous-jacent) permettant à iOS d'ajouter l'application en tant que compte de calendrier tiers direct.8
|
||||
* **Canal Android / Google Calendar :** Google Calendar ne supportant pas nativement l'ajout de serveurs CalDAV sans client tiers (tel que DAVx5) 15, le serveur Keeper.sh pousse les mises à jour en temps réel vers l'API Google Calendar via l'authentification OAuth2.8 L'appareil Android synchronise ensuite ces données de façon transparente à travers le compte Google lié au système d'exploitation.16
|
||||
* **Agrégation ICS unifiée :** L'API expose un flux iCal (.ics) sécurisé et mis en cache, lisible par n'importe quelle application de calendrier sur smartphone (Outlook, Apple Calendar ou Google Calendar), garantissant une compatibilité universelle en lecture seule.8
|
||||
|
||||
## **Architecture multi-bases de données SQL et isolation des utilisateurs**
|
||||
|
||||
Afin de garantir une résilience maximale et un cloisonnement propre des données tout en maintenant des performances élevées, l'application s'appuie sur une structure multi-bases de données SQL (utilisant principalement SQLite en mode Write-Ahead Logging \- WAL et PostgreSQL pour les services hautement concurrents).
|
||||
|
||||
\+---------------------------------------------------------------------------------+
|
||||
| APPLICATION GATEWAY |
|
||||
\+---------------------------------------------------------------------------------+
|
||||
| | |
|
||||
v v v
|
||||
\+-------------------+ \+-------------------+ \+-------------------+
|
||||
| Vikunja API | | PocketBase | | Keeper.sh |
|
||||
\+-------------------+ \+-------------------+ \+-------------------+
|
||||
| | |
|
||||
v v v
|
||||
\+-------------------+ \+-------------------+ \+-------------------+
|
||||
| SQLite / Postgres| | Multi-DB SQLite | | PostgreSQL |
|
||||
| (todos.db) | | (courses, notes) | | (calendars.db) |
|
||||
\+-------------------+ \+---------+---------+ \+-------------------+
|
||||
|
|
||||
\+-----------------+-----------------+
|
||||
| |
|
||||
v v
|
||||
\+---------------------+ \+---------------------+
|
||||
| courses.db | | notes.db |
|
||||
\+---------------------+ \+---------------------+
|
||||
|
||||
### **1\. Partitionnement physique des bases de données SQL**
|
||||
|
||||
L'architecture de stockage isole les différents domaines fonctionnels dans des fichiers de bases de données physiques distincts afin de simplifier les sauvegardes, de limiter le rayon d'impact en cas de corruption et de optimiser les performances d'accès concurrentiel :
|
||||
|
||||
* **users.db / auth.db :** Géré par PocketBase pour l'authentification unifiée, les profils des utilisateurs, les clés API de l'application et la configuration globale de l'interface.
|
||||
* **todos.db :** Base SQLite gérée nativement par Vikunja contenant l'arborescence des tâches, l'état d'avancement et la structure des buckets Kanban.
|
||||
* **courses.db :** Base SQLite gérée par PocketBase stockant l'historique d'achat, les listes de courses hebdomadaires courantes et les listes types.
|
||||
* **notes.db :** Base de données dédiée aux notes diverses ("pense-bête"), optimisée pour la recherche textuelle à grande échelle.38
|
||||
* **calendars.db :** Base PostgreSQL gérée par Keeper.sh contenant l'état des abonnements de calendrier, la file d'attente Redis pour les tâches asynchrones de synchronisation et l'historique de réconciliation des événements.9
|
||||
|
||||
### **2\. Gestion multi-utilisateurs et règles de partage de l'information**
|
||||
|
||||
Le système intègre nativement la gestion de plusieurs utilisateurs avec un contrôle d'accès strict (RBAC \- Role-Based Access Control) mis en œuvre au niveau de la passerelle et de PocketBase :
|
||||
|
||||
* **Isolation stricte (Par défaut) :** Chaque enregistrement de tâche, note ou article de course est lié à l'identifiant unique de l'utilisateur (user\_id). Les API rules de PocketBase interdisent toute lecture ou modification par un tiers si la règle Row-Level Security (RLS) user\_id \= @request.auth.id n'est pas validée.
|
||||
* **Partage d'information granulaire :** Pour permettre la collaboration (par exemple, une liste de courses partagée au sein d'un foyer), une collection d'association shares définit les droits accordés. Le schéma de partage comprend :
|
||||
JSON
|
||||
{
|
||||
"id": "share\_id",
|
||||
"resource\_type": "courses\_list | notes",
|
||||
"resource\_id": "target\_uuid",
|
||||
"shared\_by": "user\_id\_owner",
|
||||
"shared\_with": "user\_id\_recipient",
|
||||
"access\_level": "read | write"
|
||||
}
|
||||
|
||||
Les API de consultation de PocketBase appliquent alors un filtre logique pour renvoyer les objets appartenant à l'utilisateur *ou* partagés avec lui :
|
||||
![][image2]
|
||||
|
||||
## **Module de notes diverses ("Pense-bête") et recherche à grande échelle**
|
||||
|
||||
Le module "Pense-bête" est conçu pour accueillir des milliers de notes textuelles non structurées (telles que des références de pièces mécaniques, des adresses, des idées à la volée ou des mémos d'achats passés) et garantir qu'un utilisateur puisse instantanément retrouver une information spécifique sur son smartphone en utilisant des filtres par étiquettes (tags), par dates de saisie ou par recherche textuelle approfondie.
|
||||
|
||||
### **1\. Indexation et recherche plein texte (FTS5) avec SQLite**
|
||||
|
||||
Pour assurer une recherche instantanée parmi des milliers de fiches mémo sans alourdir l'empreinte mémoire du serveur mobile, PocketBase s'appuie sur le moteur d'indexation plein texte **FTS5** (Full-Text Search) natif de SQLite.
|
||||
Une table virtuelle FTS5 est configurée dans la base notes.db pour indexer le contenu et les métadonnées de chaque note :
|
||||
|
||||
SQL
|
||||
\-- Création de la table virtuelle FTS5 optimisée
|
||||
CREATE VIRTUAL TABLE notes\_fts USING fts5(
|
||||
id UNINDEXED,
|
||||
content,
|
||||
tags,
|
||||
tokenize="porter unicode61"
|
||||
);
|
||||
|
||||
*Note sur l'optimisation :* L'utilisation du tokenizer porter permet la racinisation (stemming) automatique des mots (par exemple, rechercher "courroies" ou "motoculteurs" trouvera également "courroie" ou "motoculteur").
|
||||
Lorsqu'un utilisateur recherche la référence d'une courroie de motoculteur avec la boutique d'achat, l'application exécute la requête SQL suivante qui tire parti de la puissance de l'index FTS5 :
|
||||
|
||||
SQL
|
||||
SELECT notes.id, notes.content, notes.tags, notes.created\_at, ts\_headline('notes\_fts', content, '\<b\>', '\</b\>') as highlight
|
||||
FROM notes
|
||||
JOIN notes\_fts ON notes.id \= notes\_fts.id
|
||||
WHERE notes\_fts MATCH 'courroie motoculteur'
|
||||
ORDER BY rank;
|
||||
|
||||
### **2\. Typologie et structure des métadonnées**
|
||||
|
||||
Chaque note peut être enrichie d'un système d'étiquettes dynamiques et de marqueurs temporels afin de faciliter le tri croisé :
|
||||
|
||||
* **Schéma de données d'une note :**
|
||||
JSON
|
||||
{
|
||||
"id": "uuid\_v4",
|
||||
"user\_id": "relation\_users",
|
||||
"content": "Référence courroie motoculteur Honda F560 : 4L-830 ou F12-329. Acheté chez Motoculture 77 à Meaux.",
|
||||
"tags": \["bricolage", "mécanique", "référence", "achat"\],
|
||||
"created\_at": "2026-05-23T14:00:00Z",
|
||||
"updated\_at": "2026-05-23T14:00:00Z"
|
||||
}
|
||||
|
||||
* **Ergonomie de filtrage sur l'interface SvelteKit :** Une barre de recherche à auto-complétion permet de combiner la saisie textuelle avec des jetons de recherche prédéfinis (ex. : \#mécanique pour filtrer par tag, ou after:2026-01-01 pour restreindre temporellement), offrant une navigation rapide et précise même sur de très grands volumes de données.
|
||||
|
||||
## **Conception du client SvelteKit (PWA et Thème)**
|
||||
|
||||
L'interface utilisateur de l'application est développée sous la forme d'un client unique adaptatif conçu avec SvelteKit.11 Elle remplit un double rôle : d'une part, offrir un tableau de bord complet sur ordinateur portable pour l'administration et l'affichage des tableaux Kanban complexes 2 ; d'autre part, proposer une Progressive Web App (PWA) optimisée pour les smartphones sous iOS et Android.11
|
||||
|
||||
### **Intégration du thème et des variables de design**
|
||||
|
||||
L'application consomme un thème de design fourni au moyen de variables CSS globales (Custom Properties) injectées au niveau de la racine du document (:root). Cette architecture permet une modification dynamique des teintes pour s'adapter aux modes clair et sombre tout en respectant la charte graphique globale.
|
||||
|
||||
CSS
|
||||
:root {
|
||||
\--primary-color: \#6366f1;
|
||||
\--primary-hover: \#4f46e5;
|
||||
\--bg-app: \#f8fafc;
|
||||
\--bg-card: \#ffffff;
|
||||
\--text-main: \#0f172a;
|
||||
\--text-muted: \#64748b;
|
||||
\--border-radius-m: 12px;
|
||||
\--touch-target-size: 48px;
|
||||
}
|
||||
|
||||
### **Ergonomie mobile : Todo et liste de courses**
|
||||
|
||||
Pour répondre à l'impératif d'efficacité sur smartphone, l'interface utilisateur s'articule autour de patrons de conception spécifiques :
|
||||
|
||||
* **Saisie rapide des tâches :** Le formulaire de création de tâches est accessible en un clic via un bouton d'action flottant. Il comporte des puces de sélection rapide de date d'échéance basées sur une arithmétique temporelle simple (![][image3] jour, ![][image4] jours, semaine suivante) pour s'affranchir du sélecteur de date natif souvent laborieux sur mobile.
|
||||
* **Modification instantanée :** Chaque tâche affichée dans la liste mobile dispose d'actions rapides par glissement (swipe). Faire glisser une tâche vers la droite permet de l'archiver ou de la marquer comme terminée ; la faire glisser vers la gauche ouvre un volet de report rapide (« Reporter d'un jour », « Reporter d'une semaine ») qui met à jour l'échéance via une requête API asynchrone instantanée.
|
||||
* **Ergonomie en grande surface :** L'affichage de la liste de courses adopte des éléments tactiles aux dimensions surélevées conformes aux exigences d'accessibilité mobile (hauteur minimale de ![][image5]). Les cases à cocher sont remplacées par des boutons d'activation couvrant l'intégralité de la largeur de la ligne pour permettre un marquage facile d'une seule main au supermarché.
|
||||
|
||||
### **Optimisation iOS et gestion du mode hors-ligne**
|
||||
|
||||
Sous iOS (moteur WebKit), le cycle de vie d'une PWA obéit à des règles de persistance strictes. Les données stockées dans le cache applicatif ou dans IndexedDB sont susceptibles d'être purgées si l'utilisateur n'ouvre pas l'application pendant 7 jours consécutifs.14 L'application SvelteKit intègre donc un mécanisme de synchronisation différentielle systématique au démarrage.
|
||||
Pour assurer le fonctionnement hors-ligne (notamment dans les zones à faible couverture réseau en grande surface), le plugin vite-plugin-pwa est configuré avec Workbox pour pré-cacher l'intégralité des routes statiques et des actifs indispensables.11
|
||||
Le fichier d'initialisation du Service Worker ci-dessous détaille la gestion du cache et l'interception robuste des notifications push requises pour éviter les révocations d'autorisation par iOS 13 :
|
||||
|
||||
JavaScript
|
||||
// src/service-worker.js
|
||||
import { precacheAndRoute } from 'workbox-precaching';
|
||||
import { registerRoute } from 'workbox-routing';
|
||||
import { NetworkFirst, CacheFirst } from 'workbox-strategies';
|
||||
|
||||
// Pré-mise en cache automatique des fichiers générés à la compilation \[12\]
|
||||
precacheAndRoute(self.\_\_WB\_MANIFEST);
|
||||
|
||||
// Stratégie pour les polices de caractères et styles statiques
|
||||
registerRoute(
|
||||
({ request }) \=\> request.destination \=== 'font' || request.destination \=== 'style',
|
||||
new CacheFirst({
|
||||
cacheName: 'static-assets-v1',
|
||||
})
|
||||
);
|
||||
|
||||
// Stratégie de mise en cache pour la page d'accueil et les fichiers indispensables au mode hors-ligne
|
||||
registerRoute(
|
||||
({ url }) \=\> url.pathname \=== '/' || url.pathname.includes('/dashboard'),
|
||||
new NetworkFirst({
|
||||
cacheName: 'navigation-cache-v1',
|
||||
})
|
||||
);
|
||||
|
||||
// Écouteur d'événements Push assurant la conformité avec iOS 16.4+
|
||||
self.addEventListener('push', event \=\> {
|
||||
let data \= { title: 'Notification', body: 'Nouvelle mise à jour système.' };
|
||||
|
||||
try {
|
||||
if (event.data) {
|
||||
data \= event.data.json(); // Analyse de la charge utile JSON
|
||||
}
|
||||
} catch (err) {
|
||||
// Fallback obligatoire si iOS envoie un format texte ou invalide
|
||||
data \= { title: 'Mise à jour', body: event.data? event.data.text() : 'Événement déclenché.' };
|
||||
}
|
||||
|
||||
// WebKit iOS exige l'appel de showNotification enveloppé dans waitUntil sous peine de révoquer les permissions
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(data.title, {
|
||||
body: data.body,
|
||||
icon: '/icons/icon-192.png',
|
||||
badge: '/icons/icon-192.png',
|
||||
tag: 'productivity-push-tag',
|
||||
renotify: true
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
## **Synchronisation bidirectionnelle multi-calendriers**
|
||||
|
||||
La synchronisation bidirectionnelle entre le calendrier autohébergé, Apple iCloud (CalDAV) et Google Calendar (API REST propriétaire) repose sur un orchestrateur capable de résoudre les divergences temporelles et de prévenir la duplication d'événements.
|
||||
|
||||
\+--------------------+ \+---------------------+
|
||||
| Apple iCloud (Cal) | | Google Calendar API |
|
||||
\+---------+----------+ \+----------+----------+
|
||||
| CalDAV | OAuth2 / REST
|
||||
| |
|
||||
\+-------------------+----------------------+
|
||||
|
|
||||
v
|
||||
\+----------------------------+
|
||||
| Orchestrateur Keeper |
|
||||
| (Moteur de Réconciliation)|
|
||||
\+-------------+--------------+
|
||||
|
|
||||
v
|
||||
\+----------------------------+
|
||||
| Base de Données Calendrier |
|
||||
| (Stockage PostgreSQL) |
|
||||
\+----------------------------+
|
||||
|
||||
### **Mécanisme de réconciliation et résolution des conflits**
|
||||
|
||||
Pour maintenir la cohérence des bases de données de calendrier sans introduire de boucles de mise à jour infinies, l'application associe à chaque événement un identifiant universel unique (UUIDv4) persistant à travers toutes les plateformes cibles. Le moteur Keeper.sh surveille les modifications apportées sur chaque serveur distant à intervalles réguliers.8
|
||||
L'intervalle de synchronisation dynamique (![][image6]) s'adapte en fonction de l'activité de l'utilisateur pour préserver les quotas d'API des services externes :
|
||||
![][image7]
|
||||
Où ![][image8] est fixé à 60 secondes pour les comptes premium/autohébergés 8, ![][image9] est un coefficient de temporisation de valeur ![][image10], et ![][image11] représente la durée écoulée depuis la dernière modification manuelle détectée sur l'application.
|
||||
La résolution des conflits d'événements s'effectue en comparant l'horodatage de dernière modification (LAST-MODIFIED au format iCalendar).8 Le tableau suivant détaille les règles de réconciliation appliquées par l'orchestrateur.
|
||||
|
||||
| Type de divergence détectée | Action corrective automatique | Résolution en cas de conflit d'horodatage |
|
||||
| :---- | :---- | :---- |
|
||||
| Événement modifié localement et inchangé sur la plateforme distante | Écriture de la mise à jour sur le serveur distant (iCloud ou Google) via l'API appropriée.8 | La version locale prévaut car l'horodatage distant est antérieur. |
|
||||
| Événement modifié sur le serveur distant et inchangé localement | Importation des données distantes et mise à jour de la base SQLite/Postgres locale.8 | La version distante prévaut car l'horodatage local est antérieur. |
|
||||
| Événement modifié simultanément des deux côtés (conflit strict) | Analyse des horodatages précis. La modification la plus récente dans le temps est conservée.27 | Le serveur distant ou local gagne selon le paramètre par défaut (ex. : "Google wins").27 |
|
||||
| Événement supprimé localement | Propagation de la requête de suppression vers l'ensemble des plateformes liées.8 | Suppression définitive pour éviter la réapparition de l'événement (effet "orphelin").8 |
|
||||
|
||||
### **Protocole d'authentification OAuth2 Google**
|
||||
|
||||
La synchronisation avec Google Calendar nécessite d'obtenir un accord d'accès de l'utilisateur par le flux d'autorisation OAuth2.19 L'application gère cette intégration en interne à l'aide d'un proxy OAuth2 pour simplifier la configuration sur l'hôte local 19 :
|
||||
|
||||
1. L'utilisateur lance l'association depuis l'interface SvelteKit (Desktop), ce qui le redirige vers l'URL d'autorisation Google munie des portées (scopes) https://www.googleapis.com/auth/calendar.19
|
||||
2. Après validation des droits, Google renvoie un code temporaire vers l'URI de redirection configurée sur la passerelle de l'application.19
|
||||
3. L'API Gateway échange ce code contre un jeton d'accès (access\_token) à durée de vie limitée (3600 secondes) et un jeton de rafraîchissement (refresh\_token) persistant.19
|
||||
4. Un processus planifié (cron) surveille l'expiration du jeton d'accès et exécute automatiquement une requête POST de rafraîchissement auprès du serveur d'authentification Google avant chaque cycle de synchronisation.19
|
||||
|
||||
## **Module de courses intelligent : prédiction et vision IA**
|
||||
|
||||
L'un des axes d'évolution majeurs de l'application est l'automatisation de la gestion des approvisionnements domestiques, s'appuyant à la fois sur un système d'analyse prédictive temporelle et sur les capacités de traitement visuel de l'agent IA Hermes.
|
||||
|
||||
### **1\. Algorithme d'analyse d'achat récurrent (Fonction d'auto-remplissage)**
|
||||
|
||||
L'application intègre un moteur d'analyse statistique en tâche de fond qui étudie l'historique d'achat hebdomadaire afin de déterminer la fréquence de consommation de chaque produit.
|
||||
|
||||
* **Calcul du score d'achat récurrent (![][image12]) :**
|
||||
Pour chaque produit ![][image13] acheté par un utilisateur, l'algorithme calcule un score d'élection hebdomadaire en appliquant un amortissement temporel exponentiel, ce qui permet de donner plus de poids aux habitudes récentes par rapport aux historiques obsolètes :
|
||||
![][image14]
|
||||
Où ![][image15] est une variable binaire égale à 1 si le produit ![][image13] a été coché/acheté durant la semaine ![][image16], et 0 sinon.
|
||||
Le facteur de pondération ![][image17] est défini par :
|
||||
![][image18]
|
||||
Où ![][image19] est la constante de décroissance temporelle (fixée par défaut à ![][image10]) et ![][image20] est le nombre total de semaines observées.
|
||||
* **Seuil de déclenchement :** Chaque fin de semaine, une tâche planifiée (cron) analyse la base courses.db. Si le score ![][image12] dépasse un seuil d'élection ![][image21], l'article est automatiquement ajouté à la liste "Pré-remplie" de la semaine suivante sous le statut *Suggéré*, l'utilisateur n'ayant plus qu'à valider ou retirer l'article d'un glissement de doigt.
|
||||
|
||||
### **2\. Analyse d'image IA (Frigo et placards) via Hermes Vision**
|
||||
|
||||
L'agent Hermes dispose de capacités natives d'analyse visuelle multi-modale (Vision & Image Paste). L'utilisateur peut prendre une photo de l'intérieur de son réfrigérateur ou de ses placards depuis son smartphone et l'envoyer directement à l'agent via le canal de communication mobile configuré (ex. : Telegram ou Discord).
|
||||
|
||||
\+------------------+ \+--------------------+ \+--------------------+
|
||||
| Utilisateur ou | | Agent Hermes | | Passerelle |
|
||||
| Smartphone (App) | | (Module Vision) | | API (PocketBase) |
|
||||
\+--------+---------+ \+---------+----------+ \+---------+----------+
|
||||
| | |
|
||||
| Envoi de la photo de l'inventaire | |
|
||||
|--------------------------------------\>| |
|
||||
| | Analyse de l'image (Vision) |
|
||||
| | & détection des articles épuisés |
|
||||
| |---------------------------- |
|
||||
| | | |
|
||||
| |\<--------------------------- |
|
||||
| | |
|
||||
| | Requête POST /api/v1/shopping/suggest |
|
||||
| |---------------------------------------\>|
|
||||
| | | Mises à jour SQLite
|
||||
| | | des articles manquants
|
||||
| | | (courses.db)
|
||||
| | |----------
|
||||
| | | |
|
||||
| | |\<---------
|
||||
| | |
|
||||
| | Confirmation JSON de l'auto-remplissage|
|
||||
| |\<---------------------------------------|
|
||||
| Notification de la liste mise à jour | |
|
||||
|\<--------------------------------------| |
|
||||
|
||||
Le traitement s'organise de la manière suivante :
|
||||
|
||||
1. **Réception et routage de l'image :** L'image est acheminée vers l'environnement d'exécution de l'agent Hermes.
|
||||
2. **Exécution du Skill Visuel :** Un outil spécifique mcp\_productivity\_analyze\_fridge s'exécute localement ou via MCP.21 Il transmet la photo au modèle d'analyse d'image (ex. : Claude 3.5 Sonnet ou GPT-4o) accompagné d'un prompt d'inventaire structuré :
|
||||
* *Instructions au modèle :* "Compare cette image de réfrigérateur/placard avec la liste de référence des denrées courantes de cet utilisateur. Identifie les produits habituellement présents mais visiblement épuisés ou en quantité critique (ex : bouteille de lait manquante, bac à légumes vide). Retourne uniquement les noms de ces produits sous forme de tableau JSON."
|
||||
3. **Mise à jour automatique :** L'agent traite le retour JSON du modèle et appelle directement l'API de PocketBase via le script d'outils du Skill pour ajouter les éléments manquants à la liste de courses de l'utilisateur sous l'étiquette "Saisie par IA".
|
||||
|
||||
## **Intégration IA : Serveur MCP et Skill Hermes Spécifique**
|
||||
|
||||
Pour permettre à l'agent d'intelligence artificielle Hermes d'agir de façon autonome sur la suite de productivité, deux canaux d'accès distincts sont déployés : un serveur MCP (Model Context Protocol) s'appuyant sur l'infrastructure d'API existante 21, et un Skill Hermes local stocké de manière native dans l'environnement de l'agent.28
|
||||
|
||||
\+-----------------------+
|
||||
| Agent Hermes |
|
||||
\+-----------+-----------+
|
||||
|
|
||||
\+------------------------+------------------------+
|
||||
| Charge à la demande | Utilise le transport
|
||||
v v
|
||||
\+------------------+------------------+ \+------------------+------------------+
|
||||
| Skill Hermes Spécifique | | Serveur MCP (HTTP SSE) |
|
||||
| (Fichiers locaux SKILL.md) | | (Enregistrement des outils API) |
|
||||
\+------------------+------------------+ \+------------------+------------------+
|
||||
| |
|
||||
\+------------------------+------------------------+
|
||||
| Requêtes HTTPS / REST
|
||||
v
|
||||
\+-----------------------+
|
||||
| API Gateway / PWA |
|
||||
\+-----------------------+
|
||||
|
||||
### **Canal 1 : Spécifications et configuration du serveur MCP (HTTP-SSE)**
|
||||
|
||||
Le serveur MCP permet à l'agent Hermes (ou à tout client compatible comme Claude Desktop ou Cursor) d'interroger dynamiquement les outils de l'application via une connexion bidirectionnelle Server-Sent Events (SSE).29
|
||||
Chaque outil exposé par le serveur MCP est enregistré au niveau de la passerelle et obéit à la convention de nommage standardisée de l'agent Hermes 21 :
|
||||
![][image22]
|
||||
Les caractères spéciaux tels que les tirets ou les points sont remplacés par des tirets bas pour assurer la compatibilité avec les API des grands modèles de langage.21 Ainsi, l'outil create-todo du serveur productivity est exposé sous le nom mcp\_productivity\_create\_todo.21
|
||||
Pour activer cette intégration au démarrage de l'agent Hermes, la configuration est ajoutée directement dans le fichier \~/.hermes/config.yaml sous la clé mcp\_servers 21 :
|
||||
|
||||
YAML
|
||||
\# \~/.hermes/config.yaml
|
||||
mcp\_servers:
|
||||
productivity:
|
||||
url: "https://app.example.com/mcp" \[30, 31\]
|
||||
connect\_timeout: 60 \[21\]
|
||||
timeout: 120 \[21\]
|
||||
headers:
|
||||
Authorization: "Bearer ${PRODUCTIVITY\_API\_TOKEN}" \[21, 32\]
|
||||
|
||||
### **Canal 2 : Conception d'un Skill Hermes spécifique**
|
||||
|
||||
Contrairement au protocole MCP qui nécessite une exposition réseau constante et une communication RPC formelle 33, le système de « Skills » de l'agent Hermes s'appuie sur des documents de connaissances locaux stockés directement dans le répertoire utilisateur \~/.hermes/skills/.28 Ces fichiers respectent la norme ouverte agentskills.io et privilégient une approche de divulgation progressive afin de réduire la consommation de jetons (tokens) lors des échanges avec le modèle de langage.28
|
||||
Le Skill Hermes développé spécifiquement pour l'application combine des instructions en langage naturel rédigées dans l'en-tête (frontmatter YAML) et un script Python d'exécution directe 28 :
|
||||
|
||||
# **\~/.hermes/skills/productivity\_manager.md**
|
||||
|
||||
name: productivity\_manager
|
||||
description: Gestionnaire local de tâches, de listes de courses et de calendriers de l'utilisateur.
|
||||
config:
|
||||
api\_url: "[https://app.example.com/api/v1](https://app.example.com/api/v1)"
|
||||
api\_token: "${PRODUCTIVITY\_API\_TOKEN}"
|
||||
dependencies:
|
||||
|
||||
* httpx
|
||||
|
||||
# **Instructions d'utilisation par l'agent**
|
||||
|
||||
Lorsqu'un utilisateur demande à planifier sa journée, à modifier ses listes de courses ou à rechercher un mémo :
|
||||
|
||||
1. Utilisez les outils intégrés pour interroger ou modifier l'application de productivité.
|
||||
2. Pour tout report de tâche, privilégiez l'envoi d'une date révisée au format ISO-8601.
|
||||
3. Pour la recherche de notes mémos ("pense-bête"), utilisez l'outil de recherche plein texte en spécifiant les mots-clés essentiels (ex: "motoculteur").
|
||||
4. Si l'utilisateur est en déplacement (détecté par le contexte), optimisez les retours d'informations sous forme de listes synthétiques de moins de 15 mots pour faciliter la lecture sur écran de smartphone.
|
||||
|
||||
# **Outils exécutables associés**
|
||||
|
||||
Le fichier Python ci-dessous, placé dans le répertoire des extensions de l'agent Hermes, implémente la logique d'exécution directe pour ce Skill 35 :
|
||||
|
||||
Python
|
||||
\# \~/.hermes/plugins/productivity\_tool.py
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import httpx
|
||||
|
||||
logger \= logging.getLogger(\_\_name\_\_)
|
||||
|
||||
def check\_productivity\_requirements() \-\> bool:
|
||||
"""Valide la présence des variables d'environnement obligatoires.\[35\]"""
|
||||
return bool(os.getenv("PRODUCTIVITY\_API\_TOKEN"))
|
||||
|
||||
def execute\_api\_call(endpoint: str, method: str \= "GET", data: dict \= None) \-\> str:
|
||||
"""Exécute un appel HTTP sécurisé vers l'API Gateway de la suite de productivité.\[35\]"""
|
||||
api\_url \= os.getenv("PRODUCTIVITY\_API\_URL", "https://app.example.com/api/v1")
|
||||
token \= os.getenv("PRODUCTIVITY\_API\_TOKEN")
|
||||
|
||||
headers \= {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
try:
|
||||
with httpx.Client(timeout=15.0) as client:
|
||||
if method \== "POST":
|
||||
response \= client.post(f"{api\_url}/{endpoint}", json=data, headers=headers)
|
||||
elif method \== "PATCH":
|
||||
response \= client.patch(f"{api\_url}/{endpoint}", json=data, headers=headers)
|
||||
else:
|
||||
response \= client.get(f"{api\_url}/{endpoint}", headers=headers)
|
||||
|
||||
return json.dumps(response.json()) \# Retour obligatoire sous forme de chaîne JSON \[35\]
|
||||
except Exception as e:
|
||||
return json.dumps({"error": f"Échec de l'appel API : {str(e)}"}) \# Enveloppe d'erreur standard \[35\]
|
||||
|
||||
\# Schéma d'intégration de l'outil pour le modèle de langage \[35\]
|
||||
PRODUCTIVITY\_SCHEMA \= {
|
||||
"name": "manage\_productivity\_item",
|
||||
"description": "Permet de créer, modifier ou décaler des tâches, d'ajouter des articles de course ou de rechercher des mémos.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": \["create\_task", "delay\_task", "add\_shopping", "search\_memos"\],
|
||||
"description": "L'action précise à réaliser sur la suite de productivité."
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "Le titre de la tâche, le nom de l'article de course, ou les mots-clés de recherche de note."
|
||||
},
|
||||
"task\_id": {
|
||||
"type": "integer",
|
||||
"description": "L'identifiant unique de la tâche à modifier (obligatoire pour delay\_task)."
|
||||
},
|
||||
"due\_date": {
|
||||
"type": "string",
|
||||
"description": "La nouvelle date d'échéance au format ISO-8601 (AAAA-MM-JJ)."
|
||||
}
|
||||
},
|
||||
"required": \["action"\]
|
||||
}
|
||||
}
|
||||
|
||||
\# Enregistrement de l'outil au sein du registre d'outils de l'agent Hermes \[35\]
|
||||
from tools.registry import registry
|
||||
registry.register(
|
||||
name="manage\_productivity\_item",
|
||||
toolset="productivity",
|
||||
schema=PRODUCTIVITY\_SCHEMA,
|
||||
handler=lambda args, \*\*kw: execute\_api\_call(
|
||||
endpoint="tasks" if args.get("action") not in \["add\_shopping", "search\_memos"\] else ("shopping" if args.get("action") \== "add\_shopping" else "memos/search"),
|
||||
method="POST" if args.get("action")\!= "delay\_task" else "PATCH",
|
||||
data={
|
||||
"id": args.get("task\_id"),
|
||||
"title": args.get("title"),
|
||||
"due\_date": args.get("due\_date"),
|
||||
"query": args.get("title") if args.get("action") \== "search\_memos" else None
|
||||
}
|
||||
),
|
||||
check\_fn=check\_productivity\_requirements,
|
||||
requires\_env=
|
||||
)
|
||||
|
||||
## **Plan d'action pour le développement et la mise en œuvre**
|
||||
|
||||
La réalisation pratique de cette suite de productivité s'organise selon une feuille de route progressive structurée en cinq phases de développement distinctes, séparant le cœur applicatif des évolutions d'automatisation intelligente prévues pour un second temps :
|
||||
|
||||
### **Phase 1 : Déploiement de l'infrastructure de base (Semaines 1-2)**
|
||||
|
||||
* Configurer le fichier d'orchestration Docker Compose pour initialiser PostgreSQL, Redis, PocketBase et le proxy Caddy.
|
||||
* Initialiser les structures des bases de données de base SQLite et PostgreSQL pour l'application.
|
||||
* Générer les identifiants d'API sur Google Cloud Console 26 et sécuriser l'accès aux variables d'environnement pour l'authentification OAuth2.19
|
||||
* Configurer le serveur Gitea pour lier le dépôt de code principal au point de terminaison de réception des webhooks de l'API Gateway.20
|
||||
|
||||
### **Phase 2 : Implémentation du moteur de synchronisation et authentification (Semaines 3-4)**
|
||||
|
||||
* Déployer l'instance Keeper.sh et connecter les différents comptes de calendrier distants (Apple iCloud et Google Calendar).8
|
||||
* Valider les scénarios de réconciliation de données temporelles et s'assurer de l'absence de création d'événements doublons ou orphelins lors de modifications simultanées.8
|
||||
* Configurer les règles de sécurité Row-Level Security (RLS) et la gestion des utilisateurs au sein de PocketBase pour garantir l'étanchéité des comptes.
|
||||
* Exposer le flux iCal (.ics) crypté à destination des appareils mobiles pour le mode lecture seule.8
|
||||
|
||||
### **Phase 3 : Développement de l'interface SvelteKit (Semaines 5-6)**
|
||||
|
||||
* Élaborer l'architecture CSS globale en important les variables de design fournies afin de garantir la cohérence thématique.
|
||||
* Coder les vues SvelteKit adaptatives : vue Kanban pour ordinateur portable, formulaires tactiles simplifiés pour l'ajout rapide de tâches et liste de courses optimisée pour l'usage hors-ligne en grande surface.11
|
||||
* Implémenter l'interface Web pour la gestion des notes "pense-bête", intégrant la saisie à la volée d'étiquettes (tags) et la recherche textuelle.
|
||||
* Configurer le Service Worker Workbox pour assurer la gestion rigoureuse du cache d'actifs et le traitement sans échec des événements de notification Push sous iOS.13
|
||||
|
||||
### **Phase 4 : Raccordement de l'intelligence artificielle (Semaine 7\)**
|
||||
|
||||
* Déployer le module de serveur MCP (HTTP-SSE) sur la passerelle d'API et valider l'interrogabilité des outils à l'aide de l'inspecteur MCP (mcp-inspector).30
|
||||
* Installer le Skill Hermes spécifique (productivity\_manager.md) dans le répertoire de l'agent local et configurer les jetons d'accès sécurisés au sein du fichier config.yaml de l'agent.21
|
||||
* Valider les fonctionnalités de tri et de lecture des todos par l'agent IA, ainsi que l'interrogation rapide des notes via le terminal ou le chatbot mobile.
|
||||
|
||||
### **Phase 5 : Évolutions futures (Étape 2\)**
|
||||
|
||||
* **Partage inter-utilisateurs :** Déploiement de la logique d'autorisation de partage dynamique pour les listes de courses et les pense-bêtes via la table d'association shares.
|
||||
* **Indexation plein texte à grande échelle :** Raccordement de la table virtuelle SQLite FTS5 pour assurer des recherches instantanées parmi plus de 10 000 mémos techniques ou personnels.38
|
||||
* **Moteur prédictif hebdomadaire :** Intégration du script d'analyse d'achat récurrent s'appuyant sur l'historique de la base courses.db et l'auto-remplissage basé sur le score ![][image12] calculé.
|
||||
* **Analyse visuelle d'inventaire :** Raccordement du canal Hermes Vision pour permettre l'envoi de photos de réfrigérateurs/placards via Telegram ou Discord, assurant l'extraction IA automatique des articles manquants à ajouter à la liste de courses.
|
||||
|
||||
#### **Sources des citations**
|
||||
|
||||
1. Documentation \- Vikunja, consulté le mai 23, 2026, [https://vikunja.io/docs/](https://vikunja.io/docs/)
|
||||
2. API Documentation \- Vikunja, consulté le mai 23, 2026, [https://vikunja.io/docs/api-documentation/](https://vikunja.io/docs/api-documentation/)
|
||||
3. Installing \- Vikunja, consulté le mai 23, 2026, [https://vikunja.io/docs/installing/](https://vikunja.io/docs/installing/)
|
||||
4. GitHub \- go-vikunja/vikunja: The to-do app to organize your life., consulté le mai 23, 2026, [https://github.com/go-vikunja/vikunja](https://github.com/go-vikunja/vikunja)
|
||||
5. Deploy PocketBase – Open Source Firebase Alternative \- Railway, consulté le mai 23, 2026, [https://railway.com/deploy/pocketbase-open-source-firebase-alternat](https://railway.com/deploy/pocketbase-open-source-firebase-alternat)
|
||||
6. PocketBase \- Open Source realtime backend in 1 file · GitHub, consulté le mai 23, 2026, [https://github.com/pocketbase/pocketbase](https://github.com/pocketbase/pocketbase)
|
||||
7. PocketBase \- Open Source backend in 1 file, consulté le mai 23, 2026, [https://pocketbase.io/](https://pocketbase.io/)
|
||||
8. Keeper.sh — Open-Source Calendar Syncing for Google, Outlook & iCloud, consulté le mai 23, 2026, [https://www.keeper.sh/](https://www.keeper.sh/)
|
||||
9. ridafkih/keeper.sh: Calendar sync tool & universal calendar ... \- GitHub, consulté le mai 23, 2026, [https://github.com/ridafkih/keeper.sh](https://github.com/ridafkih/keeper.sh)
|
||||
10. Keeper.sh: Calendar Syncing, V2 Release : r/selfhosted \- Reddit, consulté le mai 23, 2026, [https://www.reddit.com/r/selfhosted/comments/1rwnfav/keepersh\_calendar\_syncing\_v2\_release/](https://www.reddit.com/r/selfhosted/comments/1rwnfav/keepersh_calendar_syncing_v2_release/)
|
||||
11. Create an offline-first and installable PWA with SvelteKit and workbox-precaching, consulté le mai 23, 2026, [https://www.sarcevic.dev/offline-first-installable-pwa-sveltekit-workbox-precaching](https://www.sarcevic.dev/offline-first-installable-pwa-sveltekit-workbox-precaching)
|
||||
12. SvelteKit | Frameworks \- Vite PWA \- Netlify, consulté le mai 23, 2026, [https://vite-pwa-org.netlify.app/frameworks/sveltekit.html](https://vite-pwa-org.netlify.app/frameworks/sveltekit.html)
|
||||
13. PWA Push Notifications on iOS in 2026: What Really Works \- WebCraft, consulté le mai 23, 2026, [https://webscraft.org/blog/pwa-pushspovischennya-na-ios-u-2026-scho-realno-pratsyuye?lang=en](https://webscraft.org/blog/pwa-pushspovischennya-na-ios-u-2026-scho-realno-pratsyuye?lang=en)
|
||||
14. PWA iOS Limitations and Safari Support \[2026\] \- MagicBell, consulté le mai 23, 2026, [https://www.magicbell.com/blog/pwa-ios-limitations-safari-support-complete-guide](https://www.magicbell.com/blog/pwa-ios-limitations-safari-support-complete-guide)
|
||||
15. Looking for calendar and tasks tools : r/selfhosted \- Reddit, consulté le mai 23, 2026, [https://www.reddit.com/r/selfhosted/comments/1s6node/looking\_for\_calendar\_and\_tasks\_tools/](https://www.reddit.com/r/selfhosted/comments/1s6node/looking_for_calendar_and_tasks_tools/)
|
||||
16. Easy Ways to Add CalDAV Calendar to Google Calendar \- SysTools, consulté le mai 23, 2026, [https://www.systoolsgroup.com/how-to/add-caldav-calendar-to-google-calendar/](https://www.systoolsgroup.com/how-to/add-caldav-calendar-to-google-calendar/)
|
||||
17. pimutils/vdirsyncer: Synchronize calendars and contacts. \- GitHub, consulté le mai 23, 2026, [https://github.com/pimutils/vdirsyncer](https://github.com/pimutils/vdirsyncer)
|
||||
18. Introducing vdirsyncer for migrating calendars and addressbooks \- Zimbra : Blog, consulté le mai 23, 2026, [https://blog.zimbra.com/2024/03/introducing-vdirsyncer-for-migrating-calendars-and-addressbooks/](https://blog.zimbra.com/2024/03/introducing-vdirsyncer-for-migrating-calendars-and-addressbooks/)
|
||||
19. Design for Google CalDAV support in pimsync \- WhyNotHugo (雨果), consulté le mai 23, 2026, [https://whynothugo.nl/journal/2025/03/04/design-for-google-caldav-support-in-pimsync/](https://whynothugo.nl/journal/2025/03/04/design-for-google-caldav-support-in-pimsync/)
|
||||
20. Webhooks | Gitea Documentation, consulté le mai 23, 2026, [https://docs.gitea.com/usage/repository/webhooks](https://docs.gitea.com/usage/repository/webhooks)
|
||||
21. Native Mcp — MCP client: connect servers, register tools (stdio ..., consulté le mai 23, 2026, [https://hermes-agent.nousresearch.com/docs/user-guide/skills/bundled/mcp/mcp-native-mcp](https://hermes-agent.nousresearch.com/docs/user-guide/skills/bundled/mcp/mcp-native-mcp)
|
||||
22. Gitea Official Website, consulté le mai 23, 2026, [https://about.gitea.com/](https://about.gitea.com/)
|
||||
23. Webhooks \- Gitea Documentation, consulté le mai 23, 2026, [https://docs.gitea.com/usage/webhooks](https://docs.gitea.com/usage/webhooks)
|
||||
24. Gitea API, consulté le mai 23, 2026, [https://docs.gitea.com/api/](https://docs.gitea.com/api/)
|
||||
25. Sync Google and Outlook Calendars using CalDAV \- Sync2 Cloud, consulté le mai 23, 2026, [https://cloud.sync2.com/sync-google-and-outlook-calendars-using-caldav](https://cloud.sync2.com/sync-google-and-outlook-calendars-using-caldav)
|
||||
26. Sync Google calendar using Vdirsyncer and Orage \- Ayman Bagabas, consulté le mai 23, 2026, [https://aymanbagabas.com/blog/2018/04/08/sync-google-calendar.html](https://aymanbagabas.com/blog/2018/04/08/sync-google-calendar.html)
|
||||
27. Organize and sync your calendar with khal and vdirsyncer | Opensource.com, consulté le mai 23, 2026, [https://opensource.com/article/20/1/open-source-calendar](https://opensource.com/article/20/1/open-source-calendar)
|
||||
28. Skills System | Hermes Agent \- nous research, consulté le mai 23, 2026, [https://hermes-agent.nousresearch.com/docs/user-guide/features/skills](https://hermes-agent.nousresearch.com/docs/user-guide/features/skills)
|
||||
29. Building a Server-Sent Events (SSE) MCP Server with FastAPI \- Ragie, consulté le mai 23, 2026, [https://www.ragie.ai/blog/building-a-server-sent-events-sse-mcp-server-with-fastapi](https://www.ragie.ai/blog/building-a-server-sent-events-sse-mcp-server-with-fastapi)
|
||||
30. Mounting an MCP Server in a FastAPI ASGI Application | CodeSignal Learn, consulté le mai 23, 2026, [https://codesignal.com/learn/courses/advanced-mcp-server-and-agent-integration-in-python/lessons/mounting-an-mcp-server-in-a-fastapi-asgi-application](https://codesignal.com/learn/courses/advanced-mcp-server-and-agent-integration-in-python/lessons/mounting-an-mcp-server-in-a-fastapi-asgi-application)
|
||||
31. panz2018/fastapi\_mcp\_sse: A working example to create a FastAPI server with SSE-based MCP support \- GitHub, consulté le mai 23, 2026, [https://github.com/panz2018/fastapi\_mcp\_sse](https://github.com/panz2018/fastapi_mcp_sse)
|
||||
32. Build an MCP Server in TypeScript: From Scratch 2026 \- Digital Applied, consulté le mai 23, 2026, [https://www.digitalapplied.com/blog/build-mcp-server-typescript-tutorial-from-scratch-2026](https://www.digitalapplied.com/blog/build-mcp-server-typescript-tutorial-from-scratch-2026)
|
||||
33. Code execution with MCP: building more efficient AI agents \- Anthropic, consulté le mai 23, 2026, [https://www.anthropic.com/engineering/code-execution-with-mcp](https://www.anthropic.com/engineering/code-execution-with-mcp)
|
||||
34. Adding Tools | Hermes Agent \- nous research, consulté le mai 23, 2026, [https://hermes-agent.nousresearch.com/docs/developer-guide/adding-tools](https://hermes-agent.nousresearch.com/docs/developer-guide/adding-tools)
|
||||
35. Integrations | Hermes Agent \- nous research, consulté le mai 23, 2026, [https://hermes-agent.nousresearch.com/docs/integrations/](https://hermes-agent.nousresearch.com/docs/integrations/)
|
||||
36. How to offline PWA? : r/sveltejs \- Reddit, consulté le mai 23, 2026, [https://www.reddit.com/r/sveltejs/comments/1dopoy2/how\_to\_offline\_pwa/](https://www.reddit.com/r/sveltejs/comments/1dopoy2/how_to_offline_pwa/)
|
||||
37. Self-Hosted Cloud Storage, Calendar and Contacts with OxiCloud \- Pinggy, consulté le mai 23, 2026, [https://pinggy.io/blog/oxicloud\_self\_hosted\_cloud\_storage/](https://pinggy.io/blog/oxicloud_self_hosted_cloud_storage/)
|
||||
|
||||
[image1]: <data:image/png;base64,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>
|
||||
|
||||
[image2]: <data:image/png;base64,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>
|
||||
|
||||
[image3]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAZCAYAAAAv3j5gAAAArElEQVR4XmNgGAUjDXABsRi6IDGADV0ABxAC4iAgvgzEJWhyBAEjEG9FF8QClgLxIyDeA8T/GciwiIUBoplYYMFApkWgYKOLRRwMoxYRYdEaID6Dhs8C8Scs4iBcANGGAmAWlaJLEALk+qgcXYIQINeiCnQJQoBciyrRJQgBUi2yY4BY1IguQQgQa1ETEN8D4m8MEIv+AvFDIN6GrAgfINYiqgBzdIFRMAooBgBoKC+ZFf17JgAAAABJRU5ErkJggg==>
|
||||
|
||||
[image4]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAZCAYAAAAv3j5gAAABNElEQVR4Xu2UP0tCURjG37DUJLKpwIaWPoM1tDe1GIFDH0GooaGhqaC+Rh/ANVwaWiKUatDZEEEQHIJAEBPqeTnn1rkvR+97sa37g99wnvPqc/94JEr4D6TgOXyFz9aj0ISCtAw8XMJbuGjX1/ALHgcDUSzAOxl66MAJ3LbrIpkivkMVfIX3MvTwCIdwy653yRQ1fiYi4MemKcrANWddIVN04WQzyZKuyIUfXxc+wOXw1nTiFOVhHQ5gC66Ht2cTp8jlBr7DPbnBVOn3DAS+wA9Pzp6aj3nJwRHsw1Wx50VzR3wE9uGmyDtkfhAHIveiKSqT+cKmyHs2PxS5lzhFNSdbgZ9wDDecfCqaIn4fbXgCl2x2Rab8LBiKQlPEFMj81/H5eYNPsOQORKEt+hN2ZJCQMDffwbY9lx5URf8AAAAASUVORK5CYII=>
|
||||
|
||||
[image5]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACsAAAAZCAYAAACo79dmAAACjElEQVR4Xu2WW8hNURSFh2uEkksupZT7PSVFSqQovHnx4pIXUkKiRMmLa5G7IvFCijyIEg8S8iIpIaHkWiSXSIQxmnP+Z5119vl/qf846Yz6OnuNNfdqnrnnWnsDDTX0f6gbeUlGZH5bsoHcJ7fJJTK+LOIfaAf5RUZn/jayk7Tz8RTyigxqiqixhpCvqEy2K/kAq26qw2RL5tVM58gJVCarPyFvTOJJqvbezKuJZpKDZA0qk+1CvsEeu+KkzuQRmRpBf6k22W9+XaH25DrpheJkpa3uiwPkCiw2tIk8JZ/IZnKEXCRvySnS1+PGufcTtpbaTrrhY/HCvUKtIKv8ulqy6tdjPidU5YnJfB+yzOceo3Sa9CfPyQPYEwrNgcVu9PEC2H0TmiIK1IPcJB18XC3ZubDFlpJnsJgvZFISM9L9lYknLXF/beYfJx/JQHINVvVmtY/MTsZFyQ4nn1HaYN3JaVjcnQiihrqXJ6sWkH8581Wo1+QN7AxvVqPI+cwrSnYPOZuMQ/thsb19PNjHebJqIfkPM1+K1lmYT+RaTd7B/l2gCupmbYK7Hqcq7vLrVKq4YtWvUrVkNV9UWb1g9Pi1uZRHrPPHUlvkldUmuIXKI2UabOOEIlkVIdVi91XFVOrh9bBN+J6cKZ9uWYdgC49NvJ6wTbWbdHJP/XuPzIoglJLV+dsv8XRyXIUdkZIqOg+2uWI9PQ3du8jHzWo+7Jz8AbtJ/1SnRGgAOQn7yHkCO5enJ/NSJKtviKPkAqxPt6N0bM2AvWAUJ5a7r3XD06t9svutpmo9m0rVjY8h/caRmSqNaTUNQ8vJ1o30fatk1+UT9Sb1aRx731H+sqg7qffie1dHXMdkrqG61W+lXaFLpGlIJwAAAABJRU5ErkJggg==>
|
||||
|
||||
[image6]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACkAAAAaCAYAAAAqjnX1AAACAklEQVR4Xu2WPUiVYRTHj6WZiBgkZi5K+AHSIIKTBBni5JSCmFtDih9DariIRgWmBFIUKjg0JIgIDS2CRg0GNfgFOgg1FA6pCCVEmZH+/5xzvY9P3Knr20vcH/x4D+fceznv83lFEiRI8H9TD/fhN7gGV+BXy23BVfge7sKf8JJ+LVim4V2Y6uRmRZssdnJ5cA9mO7lAyIJvvNxp+B1+8vJk2U8EQQu85uWqREdxzMuz+RkvFwgX4Ckvd1+0Sa5Vl2RY6OX+GfPwt+hSCCVnRRtc8Athok50qgf9QpgYEW2y2i/E4KQ9T8R4Hgsf4A+Y5hdEN9kQvAUn4VXYAbfhO3jG6rwAGuBNqz2A3fAF7JMotaK/MwBfwnSnFhPuXI7iay8foR82WVwBa5z8U4srRZuPwMaeWVwAv1h8EW7CTNGXm4PnrPYH5+EiXIKfRZvk9cerkPn8w0/q+clRZv6ORI+uEtFrlOcojy/3SHsOWy3OFb1WCUf0lcVxpchsF32JHqfG06BRdOpcpmCzxTmi1yq5JzrFcecxLLX4Cnzo1DrhBrzs5AhHMtIkZ+2XxWVwR7Rxwj8t5Rb/FZzKYdgLn4hupAicyo9ydFdfh+vwrej6HRddTqNW5+bilHNUOTvHTga87eV4jZIkmCLR44pxoDwSHZEbous1lHTBCdjmFxKEjQOBmGDwSWktuQAAAABJRU5ErkJggg==>
|
||||
|
||||
[image7]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAmwAAAAwCAYAAACsRiaAAAAEoUlEQVR4Xu3daaitUxgH8GWeyVQoH6RkHsoHUmTIPOSLqZAhY0QZCiFFpkRCQhJKMnxBGTJlHsqQSLiKzOIDMsZ6Wmudve57z7l3x5F9jt+v/u211rvP6d3v/XCenvW++6YEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw9zw0XJjnrsq5cLgIADCpjst5Yrg4B22c8+NwcQbL5ywYLgIAk++RbvxnN34r55BuPt98nrN0He+Q81F3rL8ON3fjSRT/Tm/kHDU8UMVnObybr5uzTTcHACbc5t145ZwPuvlTOct28//C6Tk/5ZyTc1/O0znr5dyb83LOVvV9x9ZjsXZXXbs754dUCpY96+sfOSfV42fX1/BMNw59wXZZN54ta6ZyrnGNW/G01Ojw2DZLo5/rz7n39nAhe3i4AABMrrW78d45N3XzvpibDXul0gmaLq/lrDF665Q4vyhEDq3zKN5eSqVIeSXn+br+W86ndf32VLb+wgo539dxdNBWqeNVcw6q4xDbis1qOe928/7YbPkwjbp7X+ask/Po6PDY7unGcZ2W6+bvpFKAx+9/r1sPHw/mAMAc8WYqnZ9J03eOolv0ah3fODi2X86zqXyOfiv3/FSKl7443TbNvC14Xc6Bw8UZPDBcGEPcO3dMN/8959acDbq1sHUqhexMouMYRXCzWyqFWCtWQ3QHV+/mzUzdOABggh2WlvxHPLpX0RVaZnhgTLEtGQXIdImu2XQdttCfVxRjz9XxDd2xeD2xjndOpSPXuk0HpFIUXVrnYcOcXbp5E9utS7oO/1TfvQvf5HwyWAsrpoXvPRvadbiQyrm33xXXMz73dH4ZLgAAk++2tGihck0q93ldkUqHKgqBg3Muzzkz5+qcc3Muqu/for43OkOzadyCbcs6jnOMAnStOn89lfOKbdMm7tfrt0SbI9Oi1+GFnDNyLsnZN5XPHNdgxzTq9v2cc1oqhec+dS1E0RVFYO+xwTy2bPsHP8ax3XChiqde2/lHodqPe9MViADABIt7yOIPeyTGzRepfAXE+ql01eKpyiO646fW119T6Vb1BdFsOSuV84rz2KOOI9FdigcIYnxKKsXS+zm35Gyf823Ok/X41zkn1HGc44OpiC3F5uJUPnt0pOJ9URg2/dZp+w6zVgidXF+/S6VgjevTF2SxTflZN2+iiIz3xcMU8eBE3F+300LvWLx2HWZKXJPY3o6HKe5I5cnQ3nmDOQAwR22Uc3QafVdZdNyuHR2eetoyiqAopv6Ngq3djxWdqvbEamzNxrg9Hdnfs9Xrf7aJn2nrUUi1G/8Xp3XuwgX1tRVs7RrEtuamqXT2Hq9rzaQ9kRnFW5wrADAPxFdntG3AZpP6Gl+jEdt/+6dR8RI38kdBN91N7pPo+LRocTWd+3OuTKUYiy5c+8y757yYygMKUaxGxy46Wl+VH5sSXbZJEcXqguEiADA/XJ/Kd37NNyulsuX7f3FnGq+rCADMQdGJiv+HEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5ou/AEZs11cugIpBAAAAAElFTkSuQmCC>
|
||||
|
||||
[image8]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAAaCAYAAAAwspV7AAAB3klEQVR4Xu2WSSiFURTHj5mlSNgYMmztyFAWWLFQSlnaEEqyUFIWhiRhI5QlpSgLQ4qFDbEwFYUMhQVJspB5+J/Ovd51lXpPn2fx/epX955z3+u8c+/37kfk4uLiX8rhO7yD+3AH3qrYFdyFh/ARPsE8+ZizzMM2GGbEFkmKSjdiCfAZxhgxR4iGy1YsHN7DUyvObNsBJ6iBFVasgKRLI1aci12wYo6QDEOtWBdJUXzWTIJhqhX7M9bhG8nW/guiSArasBNekgnPYKSd8IUykq3rthNeEgs76esT7TNDJEUV2Ql/cgQfYISdAA3wGvbAJjgL22EVbIErME2tHYQnMAkWwgM4AZvhAJyDQWrtj/CTxV1asuIm03BUjVNIzl+WmveT/AlrbkjWMI3wGIao+TnMUONvxMFNuAUvSIri64SvFo4nfq4UpmCtGsfDVyPHZ6jXmF+Sp6h6kk5puHPZxvxXTMJqNebD/GLkeCv7jDn/SF1UHRw3cnswx5j/Cu6ULoq7bBbVQbKFGrtTZlF88ecac5+pJDkLq7AYjpFs9zAsIXm74A6Ukvyl8BsFr8mHayR3KX9HK8kbyQx5ivYZvmaYAJIDq58eHnOO4yyP9YHmMa/juJ7r7wlUurg4ygfgfl6iYwxhvQAAAABJRU5ErkJggg==>
|
||||
|
||||
[image9]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAbCAYAAACnZAX6AAAAxElEQVR4XmNgGAUjBngD8X4oPgvEjUDMhCTPiMQGgxogfgrEmlC+CBC/AuJaKL8MiD2hbDAIBeL/DBCbkMFsIH4NxMxAvBeI2WASIIEHQPwQJoAEQLaADIsA4snIEmRpMoNKzEQWhIICBojcVSAWQ5YIh0okIwtCQQ4DRC4PXcIKKhGALgEEdQwQOZBrUAAoHs4D8SwkMXEGiB9WMyAMDAFiBSQ1DLJAvAGIjwDxDiBeDsTWULlJQHwLiBcxYIncUTB4AQBczSfTPPQZ5QAAAABJRU5ErkJggg==>
|
||||
|
||||
[image10]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAZCAYAAABD2GxlAAACGElEQVR4Xu2WP0hWURjGX1MaClHDAkX8s4jWYkGiLX6LixE4iYs0aoMIOoUNDYK5RFMkBFIETRH9NUnRqCmhthDNSRF10BANhVCfh/d8t3PfvNdrSXzD94PfcJ9zuOe9373nPZ9IliyZQx2chJ/gF9gHc0Izjk6FDRxd8CoshqfhFfgcNvqTfMrhGuxw12fgN3grmJGcU6ILvoSvzViaj3DP+Aae9Cf53IczJuNTbsECk8dxA66KLvZLogucEl1vUfSNdcIT/gQfvsYV+MzkKdEnazN5UrYlusAJWGnDKMpECxkx+UWXD5o8KXEFjssRCrwsWsiwyS+4/LHJkxJX4HvYA0fhNHwLa0IzPJpEC3lg8lqXc3f9DXEFjsG78vu7G4BLsDSY4ZGS/1/geQlviirRte55WUDUK+ZNmD8xeVJYIHdzEnJF15q1A6REdPCRydOb5I7Jk8IC+Y1Z2KA35M/usAs3TRawDF+ZrFm0wHaTJ4UFvrMhuC16334vY3NnNudlIdio7WAv/AkLvawetnjXcbBAbgZLK3wh+lrTNMghLY298Ae87q7PwgV4M5ihDZ2vhjeKPDMdeXAHfrADopuDp8c1d80fgPN4tOanJx3EJdEj6DP8CrtDowr7FYtkDzsIfl/zcF30QSg/n++wyJt3Dj51OY+7h6J/HI4FFsEzN2MZEu2RGQlbEj/wjIV/YqttmCXLP7APsqt38mXoMa4AAAAASUVORK5CYII=>
|
||||
|
||||
[image11]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAaCAYAAABCfffNAAABTUlEQVR4Xu2TvStGYRjGr5LPQkliVBiQzSSDxeIPYDUog5JdySAJi5QsShL52M0MFmUS/wGTlJJ8X7f7ed/3nOt9Tijj+dWvzrmv5+PcnecBcnKECnpF1zT4T8bpJ32h7ZJlMUQHtZhFJb2mR/CNttJxFOv8nm5qkMUEXaet9Im+0o7UiHIG4B80pkGMKngXbeF9GT55uzgiziz9oM0axJikq4l3m/RI32hXol7gmN7A/52Ns+fT1AihGt5Fi9QX4N3sSr1AHX2mKxrEmKKLWiRN9IG+027JjGH4R4xooNTAu7AFY8zBF9rXgCzBD0e9Bso0nddiggb4EbVueiS7oOdSK6MWfrsbNRDsBFk3h4mazbGN7b8Zdux3SnGJGXpLT37wDL6JHdW+75lAZ6iNwi/xHu0NWRG7F3fwgX/xwCYHNuhlqPUn6jk5Ob/kC6haTtNfYAB1AAAAAElFTkSuQmCC>
|
||||
|
||||
[image12]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAZCAYAAADTyxWqAAABFUlEQVR4Xu3SMUhCQRjA8S8IkYakwSaHlhoEC1psiEAai4agrWYHNyEnawz3AqM1ot0wQWhoq4YkCGpxcLO5KYzyf9yp50f5sIYg/MNveN+9d+94KjLq/7eKCu5wgzISKGHauy+wPTxj3psl8YoHbxbYMj6xoBfoDAU9HNQxPjChF+gIK3o4KPOAOVkVKenfNIIx7zqwWbyI3dBo4RZr/k3DNIltnOBJ7Kbv0v8dw2hgzpt1i+JKD11LYjfc1AvfZU5S00PXAeoIuesdnGO3e4fqFG/IS++jjyODJhbdbErsJmmxz3zZJWLYxz0enaKbdzKnMy+7xro3/3EzYk9rfgTzd/lVORxiC3G1NnQbuEBWL4z649pmjS7ov8S0/QAAAABJRU5ErkJggg==>
|
||||
|
||||
[image13]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAaCAYAAAB7GkaWAAAAfUlEQVR4XmNgGMrAHojfAHEgugQIeAPxSSBWR5cgD7gB8XYgvgnEnsgS0kC8BYiZgfgsEK9DlswGYhsgVgDif0CcjywJA61A/AOIhdAlWID4ORAvQZcAgWAg/g/EtkCsBMQtyJL9QPwEyp4NxNpIcgwmDBBvbADiEGSJkQAA9EAS9Xxtj/4AAAAASUVORK5CYII=>
|
||||
|
||||
[image14]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAmwAAABRCAYAAABv7vp/AAAEMklEQVR4Xu3dW8hlYxgH8Bc5H8Z5mhjHCCUuKMQVF6KIi7kQV5RcjRukRG6cSnLmTiEhyvmcFEIOKTElVzRyusCEifA8rfX1re81zV77m7323p9+v/q313rePXt/c/e09nsoBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYNFpkWciq9r7GyOrF4cBAJi1ayJrI+vb+7s6YwAAzIHb2te/ImdF1nXGAACYAwe1r9ms/dkdAABgvmwX2VAXAQAAAAAAAAAAAACA/79LI79GNkW+i3wf+SHyY+SnyM/t+B9bCAAAU7B35J82W7NL5PDIVaXZmy3fv2bJOwAAGMwNpWnAfqkHRtgY2bEuAgAwjENK07TdWg9sxa6R0+vijOW+cQAAU3Nm5IPIe5HjIwcuHZ64bNj+Ls3pBvPq9sjXpflb72hrOccu73Pe3ZVtDQBgcNmAnNC5f7RzPZTdS7/5bPMg/8bt2+unIzt1xgAApiKfdO3Wub+ncz2kzaVphtbXA3Pmo8g5bXauxgAApiK32MjGKQ9ef78aG1LOAXulNN/9eTU2Sj7xeqMs/lSZ8nOOjdzUXk9KrlrNzzuxHgAAmJa9IhdHvihLfx7NRuXohTcNZHVZ3k+jJ0WujXzVqeVK0pTN3Led+hmd69pLdWELTi1L57F15Ty2h+pi68K6AAAwrgNK85Sq65TSr9H4cCvJhQurFt860r2l2W9tOXIRQDoqcll7fUzkvvZ6lLPrQiWb130jD5fxm0oAgG2WT9U+qWr5c2JOqr8k8lg1NpR8SnZFXezp7fY1m8xsNlOu7jy5vd4v8nx7Pa5cLftme51P9LJh627lkY3cO6X5jq6LIjdXNQCAZXkxcnDk48hnkfvb+5SLEN5qr4eSzc8T7etyPRV5IXJ5ZENp/k97dsbXRj7t3PfxbuS30jRoC81efnbe/14W59vtU5onirXc4PeCuggAMGlXR+6OHFcPTNCXkf3r4gCyIR3CDqWZg3d9+e9cv/xZGABgUOdFnquLE3Ruaead9ZXnkJ5fF3u6sy5MUH72EaX5SfbQtpanMjiwHgBY0Q6LrKuLI2yK7FEXR3i2NN81zgKIbZUrSR8szZmpAAArUs77yu1D+joy8njkkXqgh1xQkf92Gta0r0+WZuNhB9UDACvWwp5r42bhaCgAAAaU2268XJrTDV6LvF4la6+24/m+3NQ2V33mKlAAAAAAAAAAAACYlNxwdpQ8Y/SbuggAwHzRsAEAzMAtpdlctg8NGwDADIxzILuGDQBgRvoeyL6xLgAAMB19DmS/LrI58kA9AADAcGZxIDsAAGOY5oHsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsJL9C0p6rXh2lYdpAAAAAElFTkSuQmCC>
|
||||
|
||||
[image15]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACIAAAAaCAYAAADSbo4CAAABnElEQVR4Xu2VPShGYRTHDyKJJEUJUaIUG5OPspAwGaQkMViUwSpJYWBgEQODQSYfGZSyWCQfycTAIDLYJDLgfzrP6z733Ot9DffexfurX2/POaf7nu7zPOcSJUnyT0jRgaAohzfwFX4Z72Clya9a8Q94DTNNLhRqSP7sGWZY8RySxoZVPFQOSJrpM+ssuA9bfioiopOkkROS178HW10VEZEKb0maOYZd7nS0jJE0sq0TUZIGN+AbfIcF7rSHLTigg4pGeKaD8eBtWYP9cIHkrYy7KrzwIc7VQUUhSTN/gofUChwy6wr4CR9heqwobLiJJZI5YcM3ht9Kr4ozdXAR7uiERT6cIXkO18elCG6SbIWmnaQRv/2dhlXwXicsZklGwDLJBfClBz6QM7p5vDdZ+RH4YuW5dt3K58FJOGfFNCXm95JCHog8b2pJtuA3+CDz9ylbJ4KiHl7BYjhoYqPkPQtt8FzFAqUUHsF5cm7VKcnNs5ki//MXOhNqfQi7VSx0mmEHrIa7sAw+UeKBFzixrzRP0QuSsdDgpJMk5htB6EujfX4LTQAAAABJRU5ErkJggg==>
|
||||
|
||||
[image16]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAZCAYAAAAIcL+IAAAAnUlEQVR4XmNgGAUIoAbET4G4BF0CHZgA8QUgdkaXoB1IAeJ1QHwTiLPR5OAgCog7oOzJQPwSSQ4FHABiNij7EBCfR0hhB05A/B+IA9Al0EELEP8DYiF0CXRwDIjPoQuiAx4g/g3E3egS6MCTAeI+D3QJEPAGYgsoewIQfwJiLoQ0AoBM2AbEukD8BYhzUKUR4AQDxAOHgTgUTW5IAQBAKBmkXjz29QAAAABJRU5ErkJggg==>
|
||||
|
||||
[image17]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAZCAYAAADaILXQAAABF0lEQVR4XmNgGAWjYBQMfuANxDuB+DaUDQNuQPwWiPmg/D1AvBchTRjIA/FWIGYG4vNAvB5JbjEQv0TiHwXif0DMgiSGF+QBsT0DxBKQxkIkuSdAvByJrwXEj5H4MFAJxBPRBZHBFSDejcRPBeL/QKyCJJYJxGFIfKIAyJsgg1qQxCYB8XckPgjsAGIOJL4ZA0QPKB54kcQxwF0gXgJlczNAfAIKJjGoWDIDxOUwwAbE3VD2TSA2QZLDACBXHAPi7QwQFzoxQCL0MhBvAuJahFIwAPlAFIiFgPgHlE914AvEJ9AFqQU6gLgPXZBa4DAQB6ILUgOAIvUbAyTsqQaigLgRiCOBeD+aHMWgiAFSzqwAYmk0uWEAAKcOLie/LXr3AAAAAElFTkSuQmCC>
|
||||
|
||||
[image18]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAmwAAAAzCAYAAAAq0lQuAAACcklEQVR4Xu3dy6vNURQH8O2ZlAkJSSgMlBhIBgYywUAxIWUmRXmEgRgzkORRYmYgMUHJQB6lRAZK8kiZGSj/gCRh7X6/k3V/Xud0lTP4fOrbWWvtc+94t3/3t28pAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPxsQuRcZzY2cqEzG6113QEAAP37Epmd+t2RLW29MHK1/NjAbYq8i+xq+z+ZEpnZ1vcik9MaAAADOBh5kPqbqa4ORT5E9rX92bTWr6WRW90hAAD9mV+aU7aeY6muTrazN5GJkRMjl39pTORi6sdF3qceAICkPoo82snGtP40cin1e1K9PLK1rW9EjkRmtH39Xvf37m/X5kTOtHVPPaUDAGAA0yOvU7+9/TyfZvmFhHpqNsjLCJ9TPS3yPPUAAPTheGlOzHrqiwFVPXGrlpRm05U3bZtT/Td5g7Y+cir1AAD/1dzS/M3WszTrPQ58FBmf5v/aovLjUeTavDCAZZHT3eEA7kTmRVanWT61AwD47/aWZtP2Nc2utJ+L06w63OlHo24S35ZmQzgrcm3k8kC2dQcDeBW5nPr6KNWVHgDA0HkZudvWOyIL2rqf+8uqupGrjyZ/l7wh6qk/8607BADg1+rGqb41WeV7y26nuq7fT/1o7Sw2bAAAfVsReVyaDdqayIsy8kLaSZGpkU9p9i+silwvzQncys4aAAAD2hB50h0CADA8Hpbmf3MCADCkPpbm8loAAIbMgdJcUju7uwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAkPkOBatPQlzHsNEAAAAASUVORK5CYII=>
|
||||
|
||||
[image19]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAaCAYAAACD+r1hAAAAxklEQVR4XmNgGH5gNhC/AeJWdAl8YA0Q/wdiTXQJXMCNAaKhEV0CF2AB4tdAfANdAh+YwQCxRR9dAhdwYIBoaEMTxwmYgPgZEN9Fl8AF2ID4DAPEFlM0OQzACsQbgHgmA0RDL6o0KgCF0FogngPl3wbix0DMCFeBBECKVwHxMQaIk0AAFOMgW2xgimCAGYhXAPFTIJZAEtdlgGiYgiQGBguB+AcQm6FLAMFVIH7BADEUDDihAjEwATSQDMT/gDgYXWIUDBwAAJ9PIdzPlIZYAAAAAElFTkSuQmCC>
|
||||
|
||||
[image20]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAaCAYAAABVX2cEAAABDElEQVR4XmNgGAXDFzgD8S0gfg/E/4H4IKo0GJwF4n8MEPlvQDwbVRoTbAHiewwQDZZociCQDcTLgJgJXQIdsALxGSCOYIAYthZVGgymMEB8QRDYAPFkIGYG4vtA/BeIVVBUQCxjRxPDChqB2A/KzmWAuG4aQppBCoi3I/HxggNAzAtlcwHxGwZIQItAxeKBuAjKxgv4GCCGIYMmBojr6qD85UCsi5DGDfyBuB5NTBSIvwPxKyDmBuLLqNK4ASiWrNEFgWA6A8R1c4B4EZocTnAOiFnQBRkgsQmKVZCBMWhyWIEdEJ9GF0QCoPQGMkwCXQIZuAHxAwZEFnkCxPbICqDAnAGSlUbBKBhQAADIFjDhxd8YOAAAAABJRU5ErkJggg==>
|
||||
|
||||
[image21]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE4AAAAaCAYAAAAZtWr8AAADS0lEQVR4Xu2XWahOURiGX/M8XZinCCUZylguSEhELiRy4VwqQ6IMGTrGJFy4I26EQpSZZMiYeSpzpExlDFHm9/X9+/j+dez//H/Yiv3Ue7Hf/e299lp7rW99C0hJSUn5vxhAHadOUnOCe0lRgSqmLlInqD1UOx9QAJWpudQV6jy1nqrm7g+iiqjmVFWqPbWCmuJiymQIdY9qRlWknlA9siKSYTl1iaqZuR5HPabql0TkRxXYoG+DvasWdYGa7mIWUF8D3YUNZF5osJ5Tw5x3mlrmrpNA3/GBGu28crCBW+S8fCimnlI1MteDYQOzLgqAxdynHlCXYW3UcffLZANsOntuUlsC708zHta5joF/hLoWeLmoTb2jVgfeSmSvIqWjInddEG2pL9RE52maf6T2OS8J1sAGrmXgb4d9o89PuRgJe4+WeS5m4xcGTtNTjTRxXveM5/9YEuyGtds48DXz5bcO/DiU4KOB2wSbsdpstBl4ZsHS0VbqGHUGtqTz4ir1mXrl9B7W8AQXF6JkrWSrRJ6v+urBHByGtdso8NV5+Z0DPw6lHsVfpxpmvBGw/NkrCiIzqaP4kdf6w1aaqouc1IMtAZ8wxV5Yw76RJDiC3zNwmkGKX+K88tQL2EBFNIWNgUebhX5yTnrCGpjsvErUG9i2rB0tSeKW6uaM3ybw41gFix8V+Ldhq0sbRRxasnrWp65SqPxQ0EDnabrKm+e8pIg63CrwtTnIz3dz0Lcrfmjg38j4LWCzTWWOdlrPIVhMt8DPYjgsqIPz1lKvUXbBqftKpucKUJ/vT8ajZK7v6Rr4OkGo0/kS/Xztrp47sBxWHZZvFXPAB8D6JF8DG4tyhoKi3KHcovpnUklEsmh5fKLGOE+p4xm12Hk6Ho1F6VwYoaOWNjmVG957S+3IXDeApaO6JREWo0lzynk/RQlTDxfBzog7qY1IPrd5dORS6RB1aAZsSfkkPhX2w/c7L0QxD2HLUkyDDaYvaVS2zIf1XcdMta383sXFxNIbVpKchdV0esHfRJ1QZ6Jv2oXSOa8f9ZJ6FPgh2vRuwQbwILJTklBbC2ExepcK/k5ZEf8o2oVTCkRLOaw/U/JAxa2WbEoB6Ii0NDRTUlJSUn6db5Hgyw0c/53xAAAAAElFTkSuQmCC>
|
||||
|
||||
[image22]: <data:image/png;base64,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>
|
||||
@@ -0,0 +1,275 @@
|
||||
# **Cahier des Charges & Instructions de Développement : "HomeHub"**
|
||||
|
||||
Ce document sert de spécification technique de référence et de fichier d'instructions (prompt system) pour le développement de **HomeHub**, une application d'organisation personnelle auto-hébergée (PWA) conçue pour être déployée sur Proxmox 9 (Debian 13\) et intégrée à un écosystème local (Gitea, Home Assistant, Agent Hermes via MCP).
|
||||
|
||||
## **1\. Brainstorming & Choix Technologiques**
|
||||
|
||||
### **1.1. Architecture Globale**
|
||||
|
||||
L'application doit être légère, extrêmement réactive sur mobile, et facile à maintenir/sauvegarder sur un serveur Proxmox.
|
||||
|
||||
| Brique | Option A : Monolithe Go \+ Svelte (SvelteKit) | Option B : Backend FastAPI (Python) \+ Frontend React/Vite (PWA) | Option C : Full TypeScript (Node.js/NestJS \+ React/Vite) |
|
||||
| :---- | :---- | :---- | :---- |
|
||||
| **Avantages** | \- Très faible empreinte RAM. \- Binaire unique facile à déployer. \- Rapidité de Svelte sur mobile. | \- **Idéal pour l'IA (Hermes/Ollama via Python)**. \- API auto-documentée (Swagger). \- Écosystème de traitement d'image robuste. | \- Partage de types TypeScript (DRY). \- SDK MCP officiel disponible en TS. \- Écosystème PWA extrêmement mature. |
|
||||
| **Inconvénients** | \- Écosystème de librairies d'IA/MCP plus limité en Go. | \- Deux services à orchestrer (Backend/Frontend). | \- Consommation RAM légèrement plus élevée que Go. |
|
||||
| **Décision** | | **Retenue** (avec Docker Compose pour Proxmox) | |
|
||||
|
||||
*Pourquoi l'Option B ?* L'intégration d'un agent **Hermes** pour l'analyse de photos (via Vision LLM) et l'implémentation de serveurs MCP (Model Context Protocol) s'alignent parfaitement avec l'écosystème Python (FastAPI).
|
||||
|
||||
### **1.2. Base de Données**
|
||||
|
||||
Le besoin mentionne "plusieurs bases SQL" pour séparer les domaines.
|
||||
|
||||
* **Option retenue** : Une seule instance de **PostgreSQL** (hébergée en conteneur) avec une séparation logique par **Schémas** (schema users, todos, shopping, notes, calendar, kanban). Cela simplifie grandement les sauvegardes (un seul pg\_dump), permet des requêtes de jointure complexes si nécessaire (ex: lier un Todo à une Note), tout en garantissant une isolation stricte.
|
||||
|
||||
### **1.3. Synchronisation Calendrier (Google / Apple)**
|
||||
|
||||
* **Apple Calendar (iOS)** : Utilisation d'un serveur **CalDAV** minimal intégré au backend pour que iOS puisse s'y abonner nativement, ou synchronisation bidirectionnelle via l'API iCloud.
|
||||
* **Google Calendar** : Flux d'authentification OAuth2 standardisé avec un worker asynchrone pour la synchronisation des modifications.
|
||||
|
||||
## **2\. Architecture Technique de l'Application**
|
||||
|
||||
\[ Smartphone / Laptop \] \<--- HTTPS \---\> \[ Traefik / OPNsense \]
|
||||
│
|
||||
(Réseau local 10.0.0.0/22)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ Proxmox 9 VM / LXC │
|
||||
│ ┌───────────────────────────┐ │
|
||||
│ │ Docker Compose │ │
|
||||
│ │ │ │
|
||||
│ │ \[Frontend React/PWA\] │ │
|
||||
│ │ \[Backend FastAPI\] │ │
|
||||
│ │ \[PostgreSQL\] │ │
|
||||
│ │ \[Redis \- Queue/Cache\] │ │
|
||||
│ └───────────────────────────┘ │
|
||||
└─────────────────────────────────┘
|
||||
|
||||
## **3\. Schéma de Base de Données (PostgreSQL \- Schémas Multiples)**
|
||||
|
||||
Voici la structure relationnelle cible pour guider le générateur de code :
|
||||
\-- SCHEMA: auth (Gestion Utilisateurs)
|
||||
CREATE SCHEMA IF NOT EXISTS auth;
|
||||
CREATE TABLE auth.users (
|
||||
id UUID PRIMARY KEY DEFAULT gen\_random\_uuid(),
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
password\_hash VARCHAR(255) NOT NULL,
|
||||
display\_name VARCHAR(100),
|
||||
created\_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT\_TIMESTAMP
|
||||
);
|
||||
|
||||
\-- SCHEMA: todos (Gestion des tâches)
|
||||
CREATE SCHEMA IF NOT EXISTS todos;
|
||||
CREATE TABLE todos.lists (
|
||||
id UUID PRIMARY KEY DEFAULT gen\_random\_uuid(),
|
||||
user\_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
title VARCHAR(100) NOT NULL,
|
||||
color VARCHAR(7) DEFAULT '\#3B82F6',
|
||||
is\_shared BOOLEAN DEFAULT FALSE
|
||||
);
|
||||
|
||||
CREATE TABLE todos.items (
|
||||
id UUID PRIMARY KEY DEFAULT gen\_random\_uuid(),
|
||||
list\_id UUID REFERENCES todos.lists(id) ON DELETE CASCADE,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
due\_date TIMESTAMP WITH TIME ZONE,
|
||||
postponed\_count INT DEFAULT 0, \-- Permet de suivre le nombre de décalages
|
||||
status VARCHAR(20) DEFAULT 'pending', \-- pending, completed, cancelled
|
||||
priority VARCHAR(10) DEFAULT 'medium', \-- low, medium, high
|
||||
created\_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT\_TIMESTAMP,
|
||||
updated\_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT\_TIMESTAMP
|
||||
);
|
||||
|
||||
\-- SCHEMA: shopping (Liste de courses)
|
||||
CREATE SCHEMA IF NOT EXISTS shopping;
|
||||
CREATE TABLE shopping.lists (
|
||||
id UUID PRIMARY KEY DEFAULT gen\_random\_uuid(),
|
||||
user\_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
is\_active BOOLEAN DEFAULT TRUE
|
||||
);
|
||||
|
||||
CREATE TABLE shopping.items (
|
||||
id UUID PRIMARY KEY DEFAULT gen\_random\_uuid(),
|
||||
list\_id UUID REFERENCES shopping.lists(id) ON DELETE CASCADE,
|
||||
product\_name VARCHAR(150) NOT NULL,
|
||||
quantity VARCHAR(50) DEFAULT '1',
|
||||
category VARCHAR(50), \-- Épicerie, Fruits, etc. (pour tri en magasin)
|
||||
is\_checked BOOLEAN DEFAULT FALSE,
|
||||
frequency\_score INT DEFAULT 0, \-- Pour l'auto-complétion intelligente
|
||||
last\_purchased\_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
\-- SCHEMA: notes (Pense-bête & Références)
|
||||
CREATE SCHEMA IF NOT EXISTS notes;
|
||||
CREATE TABLE notes.items (
|
||||
id UUID PRIMARY KEY DEFAULT gen\_random\_uuid(),
|
||||
user\_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
tags VARCHAR(50)\[\], \-- Array de tags pour recherche ultra-rapide
|
||||
metadata JSONB, \-- Ex: {"store": "Brico Depot", "reference": "X12-34"}
|
||||
created\_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT\_TIMESTAMP,
|
||||
updated\_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT\_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX idx\_notes\_tags ON notes.items USING gin(tags);
|
||||
CREATE INDEX idx\_notes\_fts ON notes.items USING gin(to\_tsvector('french', title || ' ' || content));
|
||||
|
||||
\-- SCHEMA: kanban (Moins urgent)
|
||||
CREATE SCHEMA IF NOT EXISTS kanban;
|
||||
CREATE TABLE kanban.boards (
|
||||
id UUID PRIMARY KEY DEFAULT gen\_random\_uuid(),
|
||||
user\_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
title VARCHAR(100) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE kanban.columns (
|
||||
id UUID PRIMARY KEY DEFAULT gen\_random\_uuid(),
|
||||
board\_id UUID REFERENCES kanban.boards(id) ON DELETE CASCADE,
|
||||
title VARCHAR(100) NOT NULL,
|
||||
position INT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE kanban.cards (
|
||||
id UUID PRIMARY KEY DEFAULT gen\_random\_uuid(),
|
||||
column\_id UUID REFERENCES kanban.columns(id) ON DELETE CASCADE,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
position INT NOT NULL,
|
||||
todo\_item\_id UUID REFERENCES todos.items(id) ON DELETE SET NULL \-- Lien optionnel
|
||||
);
|
||||
|
||||
## **4\. Spécifications Fonctionnelles détaillées (Briques Applicatives)**
|
||||
|
||||
### **Brique 1 : Calendrier & Synchronisation Bidirectionnelle**
|
||||
|
||||
* **Interne** : Calendrier natif stocké en base de données.
|
||||
* **Externe** :
|
||||
* **CalDAV** : Exposer un endpoint CalDAV (/api/caldav/) pour permettre l'abonnement direct depuis iOS (sans passer par un tiers).
|
||||
* **Google Calendar API** : Intégrer un mécanisme d'OAuth2 par utilisateur pour synchroniser un calendrier Google spécifique vers HomeHub, et inversement.
|
||||
* **API** : Endpoints REST (GET /api/calendar/events, POST /api/calendar/events) utilisables par des scripts tiers.
|
||||
|
||||
### **Brique 2 : Gestion des Todos (Focus Mobile-First)**
|
||||
|
||||
* **Saisie Rapide** : Formulaire d'ajout rapide accessible en 1 clic sur mobile avec autocomplétion intelligente (ex: écrire "Rendez-vous dentiste demain 14h" doit parser automatiquement la date et l'heure via NLP léger côté backend).
|
||||
* **Visualisation et Edition Mobile** :
|
||||
* Vue liste épurée avec boutons d'action rapide : "Reporter de 1 jour", "Reporter à la semaine prochaine", "Marquer en retard".
|
||||
* Actions par glissement (Swipe left to postpone, Swipe right to complete).
|
||||
|
||||
### **Brique 3 : Liste de Courses (Focus Grande Surface)**
|
||||
|
||||
* **Mode Magasin** :
|
||||
* Affichage optimisé avec grands composants tactiles (facile à cocher d'une seule main).
|
||||
* Tri automatique des articles par rayon/catégorie (ex: mettre tous les légumes ensemble) pour optimiser le parcours en magasin.
|
||||
* Écran de veille désactivé lors de l'affichage de la liste de courses active (via la *Wake Lock API* du navigateur).
|
||||
* **IA & Analyse Prédictive (Hermes / Vision)** :
|
||||
* **Auto-remplissage Hebdo** : Algorithme basé sur la fréquence d'achat historique. Si le "Lait" est coché toutes les semaines, il apparaît pré-rempli dans la suggestion de la semaine.
|
||||
* **Analyse de Photo (Hermes)** : Endpoint POST /api/shopping/analyze-fridge qui prend une image du réfrigérateur/placard, l'envoie à l'agent Hermes (ou LLM Vision local comme Llama-3-Vision) et renvoie la liste des ingrédients manquants détectés sous forme de suggestions de liste de courses.
|
||||
|
||||
### **Brique 4 : Notes de Référence (Pense-bête)**
|
||||
|
||||
* **Recherche Puissante** : Recherche Full-Text (FTS) PostgreSQL sur le titre, le contenu et les métadonnées (ex: chercher "courroie" ou "motoculteur" doit sortir la note instantanément).
|
||||
* **Structure Métadonnées** : Possibilité de lier des paires clé/valeur spécifiques (Magasin d'achat, Référence, Lien) à chaque note.
|
||||
|
||||
## **5\. Intégrations Spécifiques (Hermes, Gitea, MCP, Home Assistant)**
|
||||
|
||||
### **5.1. Serveur MCP (Model Context Protocol) intégré**
|
||||
|
||||
Le backend FastAPI doit embarquer un serveur MCP. Ce serveur expose des "outils" (tools) réutilisables par des agents IA externes (comme votre agent Hermes ou Claude) :
|
||||
|
||||
* get\_todos() : Liste les tâches urgentes.
|
||||
* add\_todo(title, due\_date) : Crée un todo.
|
||||
* add\_shopping\_item(product\_name) : Ajoute un élément à la liste de courses.
|
||||
* search\_notes(query) : Recherche dans les pense-bêtes.
|
||||
|
||||
### **5.2. Connecteur Gitea (https://gitea.maison43.duckdns.org/)**
|
||||
|
||||
* Permettre de lier un tableau Kanban ou une liste de Todos à un dépôt Gitea.
|
||||
* **Webhooks** : Endpoint /api/webhooks/gitea pour recevoir les notifications de tickets (issues) Gitea créés ou fermés et les synchroniser automatiquement avec le Kanban ou la liste de tâches HomeHub.
|
||||
|
||||
### **5.3. Skill Agent Hermes**
|
||||
|
||||
* Exposer une API REST documentée en OpenAPI v3 (/api/openapi.json) permettant à Hermes d'appeler l'application pour ajouter des tâches à la voix ou par commande textuelle.
|
||||
|
||||
### **5.4. Home Assistant (10.0.0.2:8123)**
|
||||
|
||||
* Exposer des capteurs via des webhooks Home Assistant.
|
||||
* Exemple : Notifier Home Assistant du nombre de tâches en retard pour l'afficher sur un dashboard mural (via REST API ou MQTT).
|
||||
|
||||
## **6\. Consignes de Code et d'Implémentation (Pour l'Assistant IA)**
|
||||
|
||||
**IMPORTANT : Respecte scrupuleusement ces règles lors de la génération du code de l'application.**
|
||||
|
||||
### **6.1. Backend : FastAPI (Python)**
|
||||
|
||||
* **Structure du projet** :
|
||||
backend/
|
||||
├── app/
|
||||
│ ├── api/ \# Endpoints divisés par domaines (auth, todos, shopping, notes, calendar, mcp)
|
||||
│ ├── core/ \# Configuration, sécurité, base de données (SQLAlchemy)
|
||||
│ ├── models/ \# Modèles SQLAlchemy (utilisant les schémas PostgreSQL spécifiés)
|
||||
│ ├── schemas/ \# Schémas Pydantic pour la validation
|
||||
│ ├── services/ \# Logique métier (synchro calendrier, intégration Hermes Vision)
|
||||
│ └── main.py \# Point d'entrée de l'application
|
||||
├── Dockerfile
|
||||
└── requirements.txt
|
||||
|
||||
* **Base de données** : Utiliser SQLAlchemy 2.0 (async) \+ Alembic pour les migrations. Respecter scrupuleusement la séparation en schémas Postgres (auth, todos, etc.).
|
||||
* **Sécurité** : JWT pour l'authentification. CORS configuré pour autoriser les requêtes du réseau local 10.0.0.0/22.
|
||||
|
||||
### **6.2. Frontend : React \+ Vite \+ Tailwind CSS \+ TypeScript**
|
||||
|
||||
* **PWA Obligatoire** :
|
||||
* Configurer @vite-pwa/plugin dans Vite pour la gestion du Service Worker (mise en cache agressive des assets pour l'utilisation offline).
|
||||
* Créer un manifest.json propre avec icônes adaptées pour iOS et Android.
|
||||
* Gérer un état "Offline" propre dans l'UI (avertir l'utilisateur si les modifications sont stockées localement en attente de synchronisation).
|
||||
* **UX Mobile-First** :
|
||||
* Utiliser des composants tactiles adaptés (hauteur minimale de clic : 48px).
|
||||
* Transitions fluides (Framermotion ou CSS transitions).
|
||||
* Support du Swipe (glissement de doigt) pour les listes d'items (Todos, Courses).
|
||||
* **Thème Visuel** :
|
||||
* Interface moderne, épurée, avec un mode sombre automatique (détection système).
|
||||
* Palette de couleurs : Tons neutres et élégants (Ardoise/Slate, Bleu Cobalt pour les actions).
|
||||
|
||||
### **6.3. Docker Compose (Prêt pour Proxmox 9\)**
|
||||
|
||||
Créer un fichier docker-compose.yml complet à la racine contenant :
|
||||
|
||||
1. Le service db (PostgreSQL 16\) avec persistance des données dans un volume.
|
||||
2. Le service redis (pour les tâches asynchrones de synchronisation de calendrier).
|
||||
3. Le service backend (FastAPI).
|
||||
4. Le service frontend (Nginx servant le build React).
|
||||
|
||||
## **7\. Plan de Développement Itératif**
|
||||
|
||||
Pour guider la création de l'application étape par étape :
|
||||
|
||||
### **Phase 1 : Socle technique & Authentification**
|
||||
|
||||
1. Mise en place de l'environnement Docker, configuration de la base de données PostgreSQL avec ses schémas.
|
||||
2. Création du backend FastAPI avec authentification utilisateur (multi-utilisateurs et gestion du partage optionnel).
|
||||
3. Initialisation du frontend React PWA avec routage et configuration du Service Worker.
|
||||
|
||||
### **Phase 2 : Brique "Todos" & "Notes" (Focus Mobile)**
|
||||
|
||||
1. Développement des endpoints CRUD pour les Todos et Notes.
|
||||
2. Création de l'interface mobile-first pour l'ajout et l'édition rapide de Todos (avec gestion des reports de date simplifiés).
|
||||
3. Développement de la recherche Full-Text pour les notes avec filtres par tags.
|
||||
|
||||
### **Phase 3 : Brique "Liste de courses" & IA**
|
||||
|
||||
1. Implémentation de la vue "Magasin" interactive (mode d'écran actif permanent via Wake Lock).
|
||||
2. Développement du service d'analyse de fréquences pour l'auto-remplissage hebdomadaire.
|
||||
3. Création de l'intégration avec Hermes/LLM Vision pour l'analyse d'image (upload de photo de frigo).
|
||||
|
||||
### **Phase 4 : Calendrier & Synchronisation Apple/Google**
|
||||
|
||||
1. Développement du serveur CalDAV interne (ou abonnement webcal).
|
||||
2. Mise en place du flux OAuth2 Google Calendar et du worker de synchronisation en arrière-plan.
|
||||
|
||||
### **Phase 5 : Intégration Écosystème (MCP, Gitea)**
|
||||
|
||||
1. Création du serveur MCP pour exposer les outils d'organisation à l'agent Hermes.
|
||||
2. Mise en place des webhooks Gitea pour l'intégration Kanban.
|
||||
@@ -24,3 +24,10 @@
|
||||
- integrer les articles de la liste transmise et de la liste boutique dans la liste magique
|
||||
- scan code-barres (Phase 5)
|
||||
- enrichissement catalogue depuis OpenFoodFacts (Phase 5)
|
||||
- sur ecran shopping le bouton + si liste vide et si liste presente bouton edit (modifier le type de l'icon en fonction de la cisonstance)
|
||||
- dans le bottom sheet edition listing si je supprime un article via '-' la case valid ne devient pas accessible
|
||||
- amelioration du visuel du texte, pour question de simplicite je saisi toujours avec des minuscules, l'ors de la validation ou de l'ajout( verifier a quel moment ?) il faudrait mettre la 1ere lettre en majuscule
|
||||
- la bootom sheet ne remonte pas toujours jusqu'au plus haut ? peut tu analyser ca
|
||||
- dans la shopping liste ( c'est idem pour todo et notes) le bouton + masque une partie de la liste prevoir de mettre le bouton au dessus de la navbar ( a droite) et donc il faut prevoir de readapter la navbar: home/todo/courses/notes / espace libre pour le bouton + ( on peut l'appeler bouton action car il pourrais changer en fonction des evolution de l'app ?)
|
||||
- en mode edition d'un article, on peut ajouter des tags qui peuvent etre utilise aussi pour la recherche d'article: je tape un mot dans le champ recherche de la liste ajout shopping et si ce mot apparait dans un tag de l'article, celui s'affiche : ex: ile flottante => tage creme dessert et ensuite lors de l'ajout d'un article, si je tape 'creme dessert' l'article ile flottante apparait car il comporte un tag 'creme dessert' autre exemple: tranche de lard a grille ( tag: lard, poitrine, ) si je tape poitrine il apparait dans la liste de recherche
|
||||
- lors de la saisi dans le champ de recherche il arrive que le filtre de recherche disparraisse et la liste des elemnt ne soit pas visible, analysee le comportement et propose une amelioration ( pourquoi homehub.maison43gil.com apparait a l'ecran on peut le masquer?) lors de la saisi dans le champ de recherche il arrive que le filtre de recherche disparraisse et la liste des elemnt ne soit pas visible, analysee le comportement et propose une amelioration ( pourquoi homehub.maison43gil.com apparait a l'ecran on peut le masquer?)
|
||||
@@ -38,3 +38,5 @@
|
||||
- **Différenciation mobile/laptop** : mobile = groupes par domaine + swipe ; laptop = tableau avec filtres date, colonnes domaines/priorité/statut
|
||||
|
||||
## En attente
|
||||
|
||||
- possibilite d'enchainer des todo ( parent/enfant
|
||||
|
||||
Regular → Executable
Regular → Executable
+1
-1
@@ -5,7 +5,7 @@ RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:alpine
|
||||
FROM public.ecr.aws/nginx/nginx:alpine
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
EXPOSE 80
|
||||
|
||||
Reference in New Issue
Block a user