Manga-drama (roman/roman web/adaptation de manga) · Pipeline en 4 stations (novel-video-pipeline)
Aperçu
Transforme de manière stable une histoire textuelle (roman web, manga, scénario original, conte illustré, etc.) en un ensemble de prompts de storyboard + images de référence + bande sonore CC0 prêts à être utilisés par des modèles texte-vers-vidéo, tout en respectant la ligne rouge de « ne pas copier les images de véritables droits d'auteur tiers ». Par défaut, la chaîne de production est orientée manga-drama (16:9 horizontal / série en plusieurs épisodes), mais elle peut être basculée selon les besoins en sous-types comme le manga-drama pour enfants ou les vidéos courtes. Le rythme de la chaîne de production s'inspire des mangas-dramas viraux actuels (forts accroches + montée émotionnelle + retournements > points forts > suspense entre épisodes), mais n'en retient que la structure méthodologique ; les personnages, l'intrigue et le style sont entièrement originaux.
Le pipeline se compose de quatre stations de travail séquentielles. Chaque station produit un ensemble JSON/répertoire convenu que la station suivante consomme. Ordre de production principal : d'abord la bibliothèque d'actifs (S2), puis les prompts de storyboard (S3) : le storyboard ne fait que disposer les actifs selon un langage cinématographique, garantissant la cohérence des personnages et la continuité spatiale.
S1 Adaptation du scénario ──script.json──▶ S2 Bibliothèque d'actifs ──character_manifest + scene_manifest (+ visual_manifest)──▶ S3 Prompts de storyboard ──storyboard.json (référence aux actifs + ancrage des images ref)──▶ S4 Audio ──audio_manifest──▶ S5 Composition ──Vidéo finale
- S1 Adaptation du scénario : Compresse le texte long/original en un « squelette narratif ou de valeur pédagogique + tableau de rythme », et génère
script.json(titre / logline / personnages / scènes / beats). - S2 Bibliothèque d'actifs (à créer en premier) : Lit l'ensemble du scénario global, crée les actifs des personnages (vues multiples) (
character_manifest.json: visual_lock + vues de face/côté/dos + pack d'images d'ancrage + fixed_prompt) et les actifs des scènes (vues multiples avant/arrière) (scene_manifest.json: axis_of_action + establishing/OTS A→B/reverse/insert), et récupère les images de référence du domaine public / CC0 (visual_manifest.json). Applique les mots négatifs du pare-feu IP. Le résultat de cette étape est la « bibliothèque d'actifs » qui sera directement référencée par S3. - S3 Prompts de storyboard (basés sur la bibliothèque d'actifs) : Développe chaque beat en plans (shots). Chaque shot référence
character_ref(personnage + vue) etscene_ref(scène + vue) à partir de la bibliothèque d'actifs, verrouille la description + la bibliothèque de mouvements de caméra (camera-movements.json) + les préréglages de la plateforme pour composer le prompt, injecte lenegative_prompt, et génèrestoryboard.json. - S4 Audio : Associe des BGM/effets sonores CC0 aux plans, génère
audio_manifest.json. - S5 Composition (boucle fermée vers la vidéo finale) : Ferme la boucle avec le
storyboard.jsonde S3 (contenant lesref_imagesd'ancrage) + leaudio_manifest.jsonde S4 pour produire la vidéo finale : génération d'image/ image-à-vidéo plan par plan (en utilisantref_imagespour verrouiller les visages) → alignement audio → montage/découpage (selonduration_setaspect_ratio) → exportation vers la plateforme → vérification finale de cohérence. Voirreferences/s5-composite.mdetscripts/build_ffmpeg_concat.pypour plus de détails.
Quand utiliser (déclenchement)
- L'utilisateur fournit une histoire/roman/roman web/manga/scénario et veut « le transformer en manga-drama / courte vidéo / storyboard / animation ».
- L'utilisateur veut « adapter un scénario, écrire des prompts de storyboard, générer un script vidéo, définir un style graphique, choisir une musique ».
- L'utilisateur a besoin de la production d'une station unique (par exemple, uniquement les prompts de storyboard, ou uniquement les images de référence du domaine public).
- L'utilisateur a une exigence forte sur le fait que les ressources soient « sans droits d'auteur / utilisables commercialement / à risque zéro de propriété intellectuelle ».
Workflow
Le workflow complet s'inspire de l'approche d'orchestration de projets open source populaires, mais le code est auto-développé et les actifs sont auto-contenus :
- ViMax (MIT, ~392 commits, pipeline d'orchestration S1→S4) → Emprunt de sa découpe en phases « histoire→storyboard→vidéo » et de son contrat de production.
- seedance-prompt-skill / ai-shortfilm-prompts (MIT) → Emprunt de leur organisation de modèles de prompts au format SKILL, utilisée comme base pour S3.
- ArcReel (AGPL) → Apprentissage architectural uniquement, pas de copie de code (contagion AGPL, interdite dans ce skill de style MIT). Voir
references/architecture-licenses.md.
Étape 0 · Réception des besoins
Confirmer quatre points : ① L'histoire source (roman web / manga / scénario original / post ou chemin) ; ② La plateforme cible (manga-drama horizontal par défaut orienté Bilibili / YouTube, voir assets/s3/platform-presets.json pour bilibili ; pour vertical Douyin / Video Account, utiliser douyin) ; ③ Le style graphique (manhua chinois / aquarelle / réaliste / ukiyo-e / illustration de livres pour enfants…) ; ④ Le sous-type et le public (par défaut manga-drama 16:9 horizontal, avec un rythme inspiré des tendances fortes mais original ; si pour enfants, appliquer en plus le pare-feu IP et exclure le contenu mature).
Station 1 · S1 Adaptation du scénario
- Charger
assets/s1/story-structure.md(modèle de structure en trois actes / rythme) +assets/s1/adaptation-method.md(méthodologie de compression : conserver le squelette de valeur, supprimer les redondances, courbe émotionnelle). - Remplir
assets/s1/beat-sheet.mdsi nécessaire (tableau de rythme). - Consulter
assets/s1/example-romeo-juliet.mdetexample-journey-to-west.mdcomme exemples d'adaptation. - Générer
script.json, voirreferences/workflow.md§Contrat S1 pour les champs.
Station 2 · S2 Bibliothèque d'actifs (à créer en premier, puis prompts de storyboard)
- Lire tous les beats / personnages / scènes de S1, créer les actifs des personnages : charger
assets/s2/character-design.md, pour chaque personnage principal générer uncharacter_manifest.json(visual_lock + fixed_prompt + vues de face/côté/dos + pack d'images d'ancrage ref_pack + règles de cohérence). - Créer les actifs des scènes : charger
assets/s2/scene-multiview.md, pour chaque scène principale générer unscene_manifest.json(géographie + axis_of_action + vues {establishing, ots_a, ots_b, reverse, insert}). - Récupérer/générer les images de référence du domaine public / CC0
visual_manifest.json(plusieurs styles : manhua chinois / aquarelle / réaliste / illustration de livres pour enfants / art Met du domaine public…), appliquer le pare-feu IP. - Appliquer le pare-feu IP :
assets/s2/negative-ip.json+assets/s2/negative-ip-firewall.md. - Barrière de licence : Accepter uniquement
Domaine public/CC0. Les actifs des personnages/scènes sont toujours originaux, il est interdit de copier directement les réglages et noms des succès populaires (voir pare-feu IP §6).
Station 3 · S3 Prompts de storyboard (basés sur la bibliothèque d'actifs)
-
Charger
assets/s3/template-library.json(bibliothèque de modèles de storyboard : types de plan / échelle / émotion) +assets/s3/camera-movements.json(bibliothèque de mouvements de caméra : 40+ mouvements + mapping rythme→mouvement + syntaxe de plateforme) +assets/s3/platform-presets.json(syntaxe de prompt, ratio, durée maximale par plateforme) +assets/s3/tag-stats.json(statistiques de fréquence des tags) +assets/s3/negative-quality.md(mots négatifs de qualité) +assets/s2/negative-ip.json(pare-feu IP). -
Chaque beat → au moins un shot ; chaque shot doit impérativement avoir
character_refetscene_ref(référence à la bibliothèque d'actifs S2), et extraire la description verrouillée du personnage à partir dufixed_promptdes actifs ; extraire 1 mouvement de caméra principal decamera-movements.json(subject_aware_map recommandé selon le type de beat) ; doit inclurenegative_prompt(concaténation du pare-feu IP + mots négatifs de qualité). -
Générer
storyboard.json:shots[{shot_id, beat_ref, character_ref[], scene_ref, ref_images[] (optionnel·ancrage direct, voir ci-dessous « Ancrage des images de référence »), camera_movement, shot_size, speed (optionnel·modificateur de vitesse slow_motion etc., voir speed_modifiers dans camera-movements.json), prompt, prompt_readable, negative_prompt, platform, aspect_ratio, duration_s, attribution}]. Utilisercamera_movement: low_angle/high_angle+speed: "slow_motion"pour les plans de pouvoir (voirassets/s2/scene-multiview.md§3 (complément) Axe du pouvoir).Ancrage des images de référence (ref_images, clé de la cohérence inter-plans) :
ref_imagesest une liste d'images d'ancrage directes pour chaque plan, plus stable que lefixed_prompttextuel (alignement avec la référence @Jimeng / Runway Gen-4 verrouillage de visage par image unique). Chaque élément est une chaîne, avec deux conventions : ① Jeton de référence aux actifschar:<char_id>:<ref_pack_clef ou three_view_clef>/scene:<scene_id>:<view>(extrait automatiquement par le script decharacter_manifest.ref_pack, puis le générateur le résout en chemin d'image réel) ; ② Chemin d'image direct / URL (par exemple/assets/char_shen_zhao_face.png). Il est recommandé de fournir 2–3 images par personnage (plan rapproché de face/profil/plan large). Jimeng autorise jusqu'à 9 images, Runway en verrouille une seule.build_storyboard.pyextrait automatiquement leref_packdu personnage enref_imagespar défaut ; on peut aussi les fournir directement au modèle image-à-vidéo lors de la composition S5 pour le verrouillage des visages/vêtements. -
Les données sous licence CC BY 4.0 doivent inclure une attribution (les actifs de cette bibliothèque sont PD/CC0, pas d'attribution par défaut ; si des données CC BY externes sont importées, l'attribution est obligatoire).
-
Accélération par échafaudage (optionnel) : Après avoir créé le
script.jsonde S1 +character_manifest.json/scene_manifest.jsonde S2, exécuterpython scripts/build_storyboard.py --project <DIR> --platform bilibilipour générer automatiquement une ébauche destoryboard.json(remplit automatiquementcharacter_ref/scene_refpar beat, sélectionne 1 mouvement de caméra principal depuissubject_aware_map, choisit l'échelle du plan selon le type de beat, concatènenegative_promptpour couvrir tous les jetons IP), puis affiner manuellement l'angle de la caméra / l'émotion / les dialogues / le rythme. Ce script est l'implémentation automatique de l'ordre « actifs d'abord », et sa sortie peut être directement validée parvalidate_pipeline.py.
Station 4 · S4 Audio
- Charger
assets/s4/audio-manifest.json(liste des fichiers audio CC0 déjà récupérés, avec catégories BGM / effets sonores / émotion : ambient / cinematic / playful / tense / romantic / lullaby etc.). - Faire correspondre l'émotion du plan : basse angoissante → section de retournement, piano doux → section émotionnelle, whoosh → transition, lullaby → section de sommeil, cartoon bounce → section vive.
- Générer
audio_manifest.json:items[{file, license, source, duration_ms}], uniquement CC0.
Station 5 · S5 Composition (boucle fermée vers la vidéo finale)
- Charger
references/s5-composite.md(SOP de composition S5 : cinq étapes en boucle fermée + matrice d'outils + liste de vérification finale de cohérence). 1.5 Résolution des images d'ancrage : Exécuterpython scripts/resolve_ref_images.py --project <DIR>pour développer chaque jetonref_images(char:<id>:<key>/scene:<id>:<view>) de chaque plan en chemins d'images réels, générantstoryboard.resolved.json(les jetons non résolus sont conservés avec un avertissement). - Génération d'image / image vers vidéo plan par plan : Utiliser les
ref_images(images réelles résolues) destoryboard.resolved.jsoncomme référence pour le verrouillage des visages/vêtements, combinées avecfixed_prompt+camera_movement+lens/stagesdecamera-movements.jsonpour alimenter Jimeng @référence / Runway Gen-4 / Kling / Seedance pour générer chaque clip. - Alignement audio : Sélectionner le BGM dans
audio_manifest.json(CC0) selon l'émotion du beat, couper les effets sonores selonduration_s. - Montage/découpage : Exécuter
python scripts/build_ffmpeg_concat.py --project <DIR> --clips-dir <DIR>/clipspour générer la liste de concaténation et la commande, exporter en 1080p selonaspect_ratio(horizontal 16:9). - Vérification finale de cohérence : Vérifier la liste §4 (personnages/scènes/axe de l'action/axe du pouvoir/durée/pare-feu IP/audio/ratio) avant publication.
Étape 6 · Validation des accès (obligatoire)
Exécuter scripts/validate_pipeline.py --project <DIR> --platform bilibili [--strict] [--content-rating {normal,child}] pour valider les contrats de production des quatre stations + cinq barrières de qualité : le negative_prompt de chaque shot de S3 doit couvrir tous les jetons IP bloqués de assets/s2/negative-ip.json (exigence forte de ne pas reproduire les images de véritables droits d'auteur tiers ; échec en mode strict si absent) ; la durée doit se situer dans l'ensemble valide de la plateforme défini par platform-presets.json, le ratio doit correspondre (manga-drama horizontal par défaut 16:9) ; la durée audio de S4 doit être comprise entre 0,3 et 180 secondes (pour les effets sonores très courts). En mode --strict, même les avertissements sont considérés comme des échecs. La synthèse vidéo n'est autorisée qu'après validation.
Classification du contenu (important · par défaut sans restriction thématique) : La validation par défaut utilise --content-rating normal — aucune limitation de classification du contenu ; les thèmes pour adultes comme les combats / la violence / le suspense / le sombre sont tous produits normalement, seule la barrière de la propriété intellectuelle et les contrats des actifs sont respectés. Ce n'est que pour la chaîne de production pour enfants qu'on ajoute --content-rating child (ou l'ancien alias --child) : cela force S1 à inclure un beat de « valeur de clôture », S3 à couvrir les jetons de sécurité enfant négatifs (violence/gore/horror/blood/mature/nudity/weapon…), et vérifie les beats pour le contenu mature (échec en mode strict). C'est-à-dire : --child est un « interrupteur de sécurité enfant » purement optionnel ; s'il n'est pas passé, le contenu du manga-drama est complètement libre, sans infantilisation.
Scripts de récupération (zéro risque IP)
Les deux scripts utilisent uniquement la bibliothèque standard et curl système, pas besoin de pip install :
# Actifs visuels S2 : illustrations multi-styles du domaine public OBI (y compris livres pour enfants/ style manhua chinois) + images Openverse CC0 + art Met du domaine public
python scripts/fetch_visual_assets.py --out <DIR> --sources obi,openverse,met
# Audio S4 : Openverse CC0 (sans clé) ; remplir la clé PIXABAY/FREESOUND pour activer le canal d'extension
python scripts/fetch_audio.py --out <DIR>
- Barrières de qualité intégrées : licence (PD/CC0), qualité d'image (côté long ≥ 1000 et côté court ≥ 600, fichier ≥ 50 Ko), dédoublonnage (SHA256 entre exécutions), barrière de provenance (uniquement sources institutionnelles), barrière de sensibilité (les contenus
mature=trued'Openverse sont systématiquement exclus, exigence forte pour toutes les plateformes). - Le transfert OBI utilise le moteur
curl(--retry 5 --retry-all-errors -k) pour contourner les problèmes SSL sporadiques deurllibde Python dans cet environnement.
Cinq barrières de qualité (valables pour toutes les stations)
- Barrière de licence : S2/S4 acceptent uniquement
Domaine public/CC0; l'importation de données CC BY 4.0 doit impérativement inclure une attribution. - Barrière de qualité d'image : Le côté long des images ≥ 1000 et le côté court ≥ 600 ; durée audio 0,3–180 s, fichier ≥ 100 Ko.
- Barrière de dédoublonnage : SHA256 persisté entre les exécutions, les images/fichiers audio en double sont automatiquement ignorés.
- Barrière de provenance : Uniquement sources institutionnelles / domaine public (OBI, Openverse, Met, Freesound CC0…) ; rejeter les sources inconnues.
- Barrière de sensibilité : Exclure tout contenu
mature(renforcé pour les enfants) ; S2/S3 injectent de force les mots négatifs du pare-feu IP pour bloquer les images protégées connues ; pour les IP de mangas-dramas / romans web populaires, on peut s'inspirer de leur thème / émotion / structure de points forts (couche méthodologique), mais il est interdit de copier intégralement (plagiat complet) les personnages individuels, les scènes individuelles ou les scènes emblématiques.
Pare-feu IP (s'inspirer, pas copier)
- Les mots négatifs constants se trouvent dans
assets/s2/negative-ip.json, couvrant les images de véritables droits d'auteur tiers comme Peppa Pig, Disney, Marvel, Sanrio, Numberblocks, My Little Pony — ce sont des limites strictes, quelle que soit l'inspiration, ne pas les reproduire (la validation de l'accès vérifiera de force la couverture des jetons). - Discipline spécifique au manga-drama (s'inspirer, pas copier) : Le « type de thème, la structure émotionnelle, les paradigmes de points forts, le langage cinématographique, le rythme » des IP de mangas-dramas / romans web populaires appartiennent à la couche méthodologique, ne sont pas protégeables par le droit d'auteur, peuvent servir de nutriment créatif, l'inspiration est encouragée ; mais le plagiat complet est interdit — c'est-à-dire qu'il ne faut pas copier les personnages individuels (même nom, même réglage), reproduire des compositions de scènes emblématiques, recopier directement le style graphique distinctif / la palette de couleurs emblématiques, ou reprendre les dialogues originaux. Même si l'utilisateur nomme un succès populaire, n'en extraire que le « squelette méthodologique réutilisable » et non l'« expression », le transformer en description originale (par exemple « protagoniste masculin en noir au destin fort + protagoniste féminin en rouge face à face »), puis l'envoyer au modèle image/vidéo, en ajoutant le double verrouillage
negative_promptpour garantir la sécurité des images de véritables droits d'auteur tiers. - Ce skill n'intègre aucun texte protégé par le droit d'auteur, il ne fait que compiler des paradigmes de structure / d'écriture ; tous les exemples sont dans le domaine public ou fournis par l'utilisateur.
Ressources
assets/s1/: Modèles de structure d'histoire, méthodologie de compression d'adaptation, tableau de rythme à remplir, normes de format de scénario, deux exemples d'adaptation.assets/s2/: Lexique de mots négatifsnegative-ip.json+ explication du pare-feu IPnegative-ip-firewall.md+ méthodologie des actifs des personnagescharacter-design.md+ méthodologie des actifs des scènesscene-multiview.md.assets/s3/: Bibliothèque de modèles de storyboardtemplate-library.json, bibliothèque de mouvements de caméracamera-movements.json, préréglages de plateformeplatform-presets.json, statistiques de tagstag-stats.json, mots négatifs de qualiténegative-quality.md.assets/s4/: Liste des fichiers audio CC0 récupérésaudio-manifest.json.scripts/:fetch_visual_assets.py(récupération d'images S2),fetch_audio.py(récupération audio S4),build_storyboard.py(génération automatique d'échafaudage storyboard S1+S2 → S3, avec extraction automatique deref_images/ sélection automatique de l'angle de pouvoirpower_dynamic/ budget de plans--target-duration/ désactivation des suggestions d'auto-amélioration--no-growth),resolve_ref_images.py(S5 : résout les jetonsref_imagesen chemins d'images réels, génèrestoryboard.resolved.json),build_ffmpeg_concat.py(S5 : génère la liste de concaténation FFmpeg + commande à partir de storyboard.json),validate_pipeline.py(validation d'accès entre stations, par défaut--content-rating normalsans restriction de contenu ;--content-rating child/--childdéclenche une barrière stricte pour enfants),collect.py(extrémité de collecte d'auto-apprentissage : renvoie la production d'une exécution + les notes humaines + les journaux d'échec au serveur de mémoire),load_learnings.py(extrémité de renvoi d'auto-apprentissage : récupère le fichierlearnings.jsondu serveur de mémoire et le met en cache localement, pour quebuild_storyboard.pypuisse lire les suggestions de modèles d'échec fréquents).memory_server/:server.py(service de mémoire sans dépendance, 4 tables SQLite + authentification Bearer + limite de vitesse),growth.py(agrégation →snapshot/learnings.json+ renvoi des priors),.env.example,README.md(déploiement Caddy HTTPS / systemd / cron).references/:architecture-licenses.md(inspiration des projets open source et limites de licence),workflow.md(contrats détaillés des quatre stations et utilisation),case-studies.md(inspiration du langage cinématographique / storyboard des mangas-dramas/courtes vidéos populaires, littérature méthodologique, pas de copie d'IP),benchmark.md(évaluation comparative : matrice de scores NVP vs ViMax/seedance/ai-shortfilm/ArcReel + tests empiriques),s5-composite.md(SOP de composition S5 : cinq étapes en boucle fermée + matrice d'outils + liste de vérification finale de cohérence).
Remarques importantes
- Les grandes bibliothèques d'images/audio ne sont pas intégrées dans le skill (pour rester portable) ; les utiliser via les scripts de récupération au moment de l'exécution, ou pointer vers votre propre répertoire de ressources CC0 (par exemple
./assets/characters,S4-Audio-library/samples/etc., des lots locaux propres). - Ce skill est une méthodologie d'orchestration + des actifs auto-contenus, il ne dépend pas de codes externes comme ViMax/ArcReel pour fonctionner.
- Après avoir obtenu une clé BHL / un jeton NYPL, on peut ajouter les canaux
fetch_bhl()/fetch_nypl()dansfetch_visual_assets.py(OBI déjà vérifié sans clé).
Auto-apprentissage (optionnel · boucle de données fermée)
Permet au skill de s'auto-améliorer avec l'utilisation : après chaque exécution du pipeline, les métriques objectives / les notes humaines / les journaux d'échec sont renvoyés dans la base de mémoire. Après agrégation, les « suggestions de modèles d'échec fréquents / notes / signaux de style » sont renvoyés au skill pour éviter ces problèmes lors de la prochaine génération. Bibliothèque standard uniquement, zéro dépendance, déployable indépendamment sur un petit serveur, découplé du skill lui-même. Voir memory_server/README.md pour le déploiement complet.
Utilisation en solo ? Pas besoin de déploiement serveur. Si
NVP_MEMORY_URLn'est pas défini,collect.py/load_learnings.pyse connecteront automatiquement au SQLite local (memory_server/data/nvp_memory.db) et effectueront l'agrégation après la collecte. Configuration zéro pour exécuter la boucle complète. Ce n'est que pour l'agrégation multi-machines / la collaboration multi-utilisateurs / le renvoi à distance qu'il faudra lancerserver.py(voir ci-dessous « Démarrage du service » et README §2).
Trois étapes en boucle fermée :
- Collecte (après exécution du pipeline) :
python scripts/collect.py --project <DIR> --platform bilibililit automatiquement les beats / shots / taux de résolution / durée etc. à partir descript.json/storyboard.json/storyboard.resolved.jsonet les écrit dans la base de mémoire (par défaut SQLite local ; siNVP_MEMORY_URLest défini, envoie une requête POST au service). On peut ajouter des notes humaines avec--feedback 5 overall "Rythme très cool", ou des journaux d'échec avec--failures fails.json. Pour les machines hors ligne, utiliser--mirror ./mirror.json --dry-runpour générer un miroir, puiscollect.py --sync ./mirror.jsonpour le synchroniser sur le serveur. Ajouter--localpour forcer le local,--remotepour forcer le passage par le service. - Agrégation (auto locale / serveur planifié) : En mode local, l'agrégation est déjà effectuée automatiquement après la collecte ; en mode HTTP, utiliser cron pour exécuter
python memory_server/growth.pytoutes les heures, qui agrège les quatre tables SQLite ensnapshot/learnings.jsonet insère/met à jour les connaissances stables dans la tablepriors. - Renvoi (avant la prochaine génération) :
python scripts/load_learnings.pylitlearnings.jsonet le met en cache dans.cache/learnings.json(par défaut lit le snapshot local ; si une URL est définie, le récupère à distance ;--localforce le local,--no-fetchlit uniquement le cache).build_storyboard.pyle lit automatiquement et affiche les « suggestions d'auto-apprentissage » lors de la génération (modèles d'échec fréquents → éviter à l'avance, par ex. images de référence non résolues, plans manquants sur l'axe du pouvoir). On peut aussi utiliser--no-growthpour le désactiver.
Démarrage du service : python memory_server/server.py --no-auth (test local) ou python memory_server/server.py (réseau public, nécessite la variable d'environnement NVP_API_TOKEN / --token ; proxy inverse Caddy pour TLS). Endpoints : GET /health, POST /ingest, GET /query, GET /snapshot, GET /export.