Interface en ligne de commande (CLI)¶
À côté de l'IHM JavaFX, VigieChiro Companion expose un point d'entrée sans interface graphique :
fr.univ_amu.iut.cli.Cli.
Il répond au besoin de scriptabilité (parcours A10 : enchaîner des imports/exports sans clics,
pour les utilisateurs avancés). La CLI n'a pas de logique propre : elle orchestre les services
métier existants (ServiceImport, ServiceLot, ServiceValidation, DAO multi-features).
Principe : réutiliser, pas réimplémenter
La CLI et l'IHM sont deux façades sur le même cœur métier. Tout ce que fait la ligne de commande, l'application graphique le fait aussi, via les mêmes services. C'est l'intérêt d'avoir isolé le métier des vues (cf. Architecture) : on peut lui greffer une seconde surface sans le dupliquer.
Comment elle s'assemble (injecteur enfant)¶
La CLI a besoin de tout le graphe applicatif (socle + features) plus quelques aides de lecture qui lui sont propres. Plutôt que de modifier la composition racine, elle crée un injecteur enfant :
L'enfant hérite de tous les bindings du socle et des features (dont les services et DAO), et y ajoute les aides CLI sans rien retirer ni remplacer. C'est le patron injecteur enfant de Guice, détaillé dans Injection (Guice).
flowchart LR
main["Cli.main()"] --> child
subgraph child["Injecteur enfant"]
CliModule["CliModule<br/>(RegistrePassages)"]
end
child -.hérite de.-> root["RacineInjecteur.creer()<br/>socle + features"]
child --> svc["ServiceImport · ServiceLot<br/>ServiceValidation · DAO"]
CliModule
n'apporte qu'une chose :
RegistrePassages,
une lecture transverse qui croise les DAO de passage et sites pour reconstituer le contexte
« carré / point » de chaque passage. La dépendance va cli → <feature>.model.dao (jamais vers une
view/viewmodel) : c'est l'unique entorse autorisée par la règle ArchUnit assouplie, et cli reste
un puits (aucune feature ne dépend de lui), donc le graphe reste acyclique.
Les sous-commandes¶
Ce tableau est tenu par un test
DocumentationAJourTest confronte ce tableau aux sous-commandes réellement câblées dans
l'annotation @Command de CommandeRacine : ajouter une commande sans lui écrire sa ligne fait
rougir la CI (#1458). Le tableau a compté jusqu'à 22 commandes sur 29 avant qu'on s'en aperçoive.
| Commande | Options | Parcours | Service |
|---|---|---|---|
creer-site |
--carre <n> [--nom ..] [--protocole ..] [--commentaire ..] |
A10 | ServiceSites.creerSite |
ajouter-point |
--site <id> --code <c> [--lat ..] [--lon ..] [--description ..] |
A10 | ServiceSites.ajouterPoint |
lister-sites |
[--json] |
A10 | ServiceSites (lecture) |
lister-passages |
[--json] |
P5 | RegistrePassages (lecture) |
statut-passage |
--passage <id> [--json] |
M-Passage, #1878 | ServicePassage.detailPassage + ResultatsIdentificationDao + ServiceConditionsPassage.heuresProuvees (lecture). La ligne « Nuit » dit l'origine de ses heures - [attestées par les enregistrements] ou [déclarées, modifiables] -, et le JSON porte heuresProuvees : un script sait ainsi avant d'essayer si metadonnees-passage --heure-debut sera refusé, au lieu de l'apprendre en échouant |
verifier-maj |
(aucune option) | #2109 | VerificateurMiseAJour (lecture seule, réseau) : indique si une version plus récente est publiée. Pendant CLI de l'annonce au démarrage de l'IHM. Trois codes de sortie, parce qu'un script ne pilote pas « à jour » et « je n'ai pas pu savoir » de la même façon : 0 à jour, 10 mise à jour disponible, 1 vérification impossible (hors ligne, ou version locale inconnue car lancée hors d'un artefact publié). Se tait dès qu'un doute existe plutôt que d'annoncer à tort ; désactivable avec la feature maj |
diagnostiquer |
--passage <id> [--json] [--csv <serie\|anomalies>] |
P6, #1672 | ServiceDiagnostic.diagnostiquer (lecture seule) : bilan matériel d'une nuit (climat R20, T° début nuit, cohérence horaire #548, GPS du point, anomalies/évènements R19). Parité CLI de M-Diagnostic. --csv exporte série ou anomalies via ExportDiagnostic (P6-CA6) |
importer |
--source <dir> --point <id> [--annee N] [--passage N] [--conserver-originaux \| --sans-originaux] |
P2 | ServiceImport. Le mode de conservation suit le réglage import.conserver-originaux (par défaut : pas de copie) ; les deux options le forcent pour un import ponctuel et s'excluent. Auparavant la commande conservait en dur, quel que soit le réglage (#2064). La sortie rapporte aussi le doublon de nuit et les anomalies du journal : l'écran les montre depuis #2044, la commande les taisait (#2004) |
importer-tadarida |
--passage <id> --csv <fichier> [--remplacer] |
P6 | ServiceValidation.importer / reimporter |
qualifier |
--passage <id> --verdict <ok\|utilisable\|inexploitable> [--commentaire ..] |
R13 | ServicePassage.poserVerdict (alias douteux/a-jeter rétro-compatibles) |
qualifier-fichier |
--passage <id> --sequence <id> --verdict <bon\|mauvais\|inexploitable> |
#1512 | Verdict par fichier d'une séquence de la sélection d'écoute ; recalcule le verdict final proposé. Parité CLI de M-Qualification |
lister-selection |
--passage <id> [--json] |
#1512 | Sélection d'écoute d'un passage : verdict par fichier de chaque séquence + verdict final proposé (dérivé). Parité CLI de M-Qualification, lecture seule |
pre-check |
--passage <id> [--json] |
#1512 | Pré-check consultatif d'une nuit (3 feux : couverture horaire, nombre de fichiers, renommage) + résumé des anomalies. Parité CLI de M-Qualification, lecture seule, jamais bloquant (R13) |
constituer-selection |
--passage <id> [--methode <reparti\|aleatoire\|manuel>] [--taille <n>] |
R12 | (Re)constitue la sélection d'écoute (échantillon à écouter). Remplace la sélection existante et efface ses verdicts. Parité CLI de « Personnaliser… » / « Régénérer » |
exporter-lot |
--passage <id> |
P4 | ServiceLot |
deposer |
--passage <id> |
P8 | ServiceLot.preparerLot + marquerDepose (marquage manuel) |
recuperer-vigiechiro |
[--token <jeton>] |
#1181, #1866 | rejoue les RapprochementVigieChiro (taxons, sites/points) après un GET /moi de contrôle ; 0 ssi connecté. Le geste ne fait que recevoir, d'où le verbe (ADR 0022) ; alias synchroniser-vigiechiro, conservé pour les scripts |
deposer-vigiechiro |
--passage <id> [--token <jeton>] [--archives\|--wav] |
#1043 | DepotVigieChiro.deposer (moteur reprenable #982, téléversement parallèle #984). Défaut = ServiceLot.fichiersDepotParDefaut, le même choix que M-Lot : ZIP si présentes, sinon invite à les générer (étape 2), sinon repli WAV. --archives/--wav forcent l'un ou l'autre |
lancer-traitement-vigiechiro |
--passage <id> [--token <jeton>] [--forcer] |
#984, #1261, #1265 | DepotVigieChiro.lancerTraitement (POST /participations/{id}/compute) — équivalent du bouton « Lancer la participation ». 0 dès lors que le traitement est en route (accepté ou déjà en cours : la commande est idempotente), 1 sinon. Une nuit déjà analysée n'est pas relancée : le serveur détruirait ses observations pour les recalculer, sans pouvoir les régénérer (audio absent d'un dépôt en archives, #1244) — --forcer lève cette garde, typiquement après un échec |
etat-traitement-vigiechiro |
--passage <id> [--token <jeton>] |
#1265 | SuiviTraitement.relever (lecture seule : GET /participations/{id} → bloc traitement, et mise à jour du cache local #1262). Codes faits pour un script : 0 terminé, 3 planifié/en cours/nouvel essai, 2 en échec, 4 jamais lancé, 1 erreur technique |
reinitialiser-depot |
--passage <id> |
#984 | ServiceLot.reinitialiserDepot (efface le plan depot_unite, retour « Prêt à déposer » ; local, archives ZIP et lien de participation conservés) — équivalent du bouton « Réinitialiser le dépôt » |
verifier-depot-vigiechiro |
--passage <id> [--token <jeton>] |
#1132 | VerificationDepot.verifier (lecture seule : journal de traitement + titres des donnees vs plan depot_unite ; 0 ssi tout est retrouvé) |
importer-vigiechiro |
--passage <id> [--remplacer] [--participation <objectid>] [--token <jeton>] |
#1181, #1838 | ImportVigieChiro.importerRapide (résultats Tadarida depuis l'API ; --participation = rattachement préalable). Au premier import, prend le CSV d'observations d'un coup (#1565) quand la plateforme l'expose, avec repli sur la pagination donnees. Avec --remplacer, reste sur les donnees : un ré-import va chercher ce qui a changé côté serveur (avis du validateur, fils), que le CSV effacerait. Le CSV ne porte ni ancrage ni fils de discussion : la publication les acquiert ensemble quand elle en a besoin (ADR 0019) |
publier-corrections-vigiechiro |
--passage <id> [--token <jeton>] |
#723, #1838 | PublicationCorrections.publier (un PATCH par observation publiable : taxon + certitude + ancrage ; idempotente, code 1 si refus). Acquiert d'abord l'ancrage qui manque (#1838) quand la nuit est rattachée à une participation : une nuit importée par CSV (#1565) n'en porte pas, ses corrections seraient sinon toutes écartées. Le rapatriement peut durer ; son avancement va sur la sortie d'erreur, la sortie standard restant réservée au bilan. Une nuit déjà ancrée n'en paie pas le coût |
archiver |
--passage <id> [--confirmer] |
#1300 | ServiceArchivagePassage.archiver : libère le disque d'une nuit déposée (séquences + bruts), après avoir capturé les empreintes. Sans --confirmer, annonce ce qui serait supprimé et sort en 2 : la suppression est irréversible, elle ne se fait pas par inadvertance |
reactiver |
--passage <id> --source <dir> [--json] |
#1302, #1406, #1571 | ServiceReactivationPassage.reactiver : rebranche les fichiers retrouvés, fichier par fichier, via la cascade de preuves. Le dossier est reconnu, pas déclaré : s'il ne contient que les bruts, les séquences sont régénérées (transformation déterministe) puis vérifiées comme n'importe quel candidat — la voie empruntée est dite (champ voie en JSON). Non destructive et idempotente. 0 si l'audio redevient complet, 1 s'il reste partiel (les écarts sont énumérés). Sur un passage reconstruit (observations sans ancrage), acquiert en plus l'ancrage plateforme par ré-import des donnees (#1571) pour rendre les corrections publiables — le seul cas où reactiver touche le réseau |
reconstruire-passage |
[--participation <objectid>] [--json] |
#1305, #1565 | ServiceReconstructionPassages : sans argument, liste les participations VigieChiro sans équivalent local (nuits déposées depuis un autre poste, ou avant l'application) ; avec --participation, en reconstruit une en passage archivé (séquences recréées + observations rapatriées). La reconstruction bascule sur le CSV d'observations téléchargé d'un coup (#1565, quasi instantanée), avec repli sur la pagination donnees si le CSV n'est pas exposé. Les lacunes sont imprimées avec le rapport |
metadonnees-passage |
(--passage <id> \| --tout) [--recuperer] [--envoyer] [--enregistreur <serie>] [--heure-debut <HH:mm> --heure-fin <HH:mm>] [--confirmer] [--json] |
#1861 | Parité CLI de la modale « Modifier le passage » : --recuperer rapatrie météo/micro/n° de série depuis la participation, --envoyer réécrit les métadonnées locales dessus (les heures y sont réalignées sur les enregistrements, #1878), --enregistreur et les heures écrivent en local sans réseau. --tout est le rattrapage de saison : sans lui, les correctifs #1814/#1828/#1844 ne réparent que la nuit sur laquelle on repasse. Comme il écrit sur la plateforme, il exige --confirmer ; sans, il énumère ce qu'il ferait. Best-effort par nuit, compte rendu nuit par nuit, code 1 s'il reste des nuits ignorées |
retro-empreintes |
(aucune option) | #1299 | BackfillEmpreintes : pose les empreintes manquantes sur toutes les nuits importées avant V23. Rejouable sans risque (ne touche que ce qui manque) |
exporter-vu |
--passage <id> --sortie <fichier> |
P7 | ServiceValidation |
exporter-observations |
--passage <id> --sortie <fichier> |
#149 | ProjectionsAudioDao.lignesAudioDuPassage + ExportObservationsCsv |
audit-coherence |
[--passage <id>] [--json] [--online] [--token <jeton>] |
#1133, #1254, #1347 | ServiceAuditCoherence : confronte disque, base et serveur. Sans --passage, audite tout le workspace ; avec, une seule nuit (utile après l'avoir réparée). --online ajoute les constats qui demandent le réseau (dépôts, points). 0 ssi aucun constat d'erreur |
sauvegarder |
[--complet] [--dossier <dir>] |
#148, #1346 | ServiceSauvegarde : instantané cohérent de la base (VACUUM INTO). --complet emporte aussi l'audio (dossiers de session) — c'est la seule sauvegarde qui protège vraiment, la plateforme ne rendant pas l'audio d'un dépôt en archives. Le bilan dit ce qui n'a pas pu être copié (carte SD non montée) et sort en 2 : une sauvegarde qu'on croit complète et qui ne l'est pas vaut moins que pas de sauvegarde |
restaurer |
--sauvegarde <chemin> [--complet] --confirmer |
#148, #1346 | ServiceSauvegarde.restaurer / restaurerComplet : remet la base (et, avec --complet, les dossiers de session). Écrase l'état local : --confirmer est obligatoire. La base courante est mise de côté (vigiechiro.db.avant-restauration) |
reset-guide |
[--json] [--executer --confirmer [--accepter-perte] [--sauvegarde <dir>]] |
#1151, #1419 | Sans --executer : lecture seule — ce que deviendrait l'audio de chaque nuit si l'on repartait d'une base neuve (disque / serveur / perdu), code 2 dès qu'une nuit est en « perdu », pour qu'un script puisse refuser d'enchaîner. Avec --executer : ServiceReset mène la procédure (sauvegarde complète → base neuve → repeuplement depuis VigieChiro → audit final). Il refuse de démarrer si la perte n'est pas acceptée, ou si VigieChiro ne répond pas — une base neuve qu'on ne peut pas repeupler est une destruction sèche. Dans les deux cas, la base reste intacte |
lister-observations |
--passage <id> [--statut ..] [--taxon ..] [--douteux] [--reference] [--certitude ..] [--json] |
#1311 | La surface de découverte de la revue. Liste les observations d'un passage avec leur identifiant, l'avis de Tadarida, le vôtre, celui du validateur, le statut et les drapeaux. Sans elle, les gestes de revue (et discussion) sont aveugles : rien ne donnait les identifiants. Ses filtres sont exactement ceux des gestes (SelectionObservations partagé) : ce qu'elle montre est ce qu'ils toucheraient |
valider-observations |
(--observation <ids> \| --passage <id> [filtres] [--confirmer]) |
R15, #1311 | Accepte la proposition de Tadarida, en lot atomique (mode Activité : traite exactement les lignes visées ; le mode Inventaire, qui propage à d'autres lignes, reste à l'écran - propager dans un script toucherait des lignes que l'utilisateur ne voit pas) |
corriger-observations |
--taxon <code> (--observation <ids> \| --passage <id> [filtres] [--confirmer]) |
R16, #1311 | Retient un autre taxon, en lot atomique. Le taxon doit exister au référentiel : un code inconnu arrête tout avant la moindre écriture |
marquer-douteux |
[--retirer] (--observation <ids> \| --passage <id> [filtres] [--confirmer]) |
#160, #1311 | Lève (ou baisse) le drapeau « douteuse ». Ce drapeau ne dit rien du taxon : il dit « je ne sais pas », une troisième réponse qui n'est ni valider ni corriger. Réversible |
marquer-reference |
[--retirer] (--observation <ids> \| --passage <id> [filtres] [--confirmer]) |
P10, #1311 | Verse (ou retire) les observations dans la bibliothèque de sons de référence - la source References de l'écran, et la matière de son export |
poser-certitude |
(--certitude <SUR\|PROBABLE\|POSSIBLE> \| --effacer) (--observation <ids> \| --passage <id> [filtres] [--confirmer]) |
#1139, #1311 | Déclare la certitude observateur. Il faut choisir explicitement : elle ne se déduit ni de la probabilité Tadarida ni d'une validation, et reste vide par défaut. C'est un jugement, que la plateforme exigera avec le taxon (#723) et qu'un naturaliste lira comme la parole de l'observateur |
discussion |
--observation <id> [--message <texte> --confirmer] |
#1417, #1418 | Le fil d'échange avec le validateur du MNHN. Sans --message, le lit (le fil vient de la base, rafraîchi à chaque import). Avec, y répond — ⚠️ écriture définitive : le serveur ajoute par $push et n'offre aucune route de suppression. --confirmer est donc obligatoire, et le message n'est écrit localement qu'après que le serveur l'a accepté |
--help / -h, --version / -V, ou aucun argument |
— | — | — |
Socle : registre de commandes picocli (#614)¶
Le CLI repose sur picocli 4.7.7 : chaque commande est une classe annotée
@Command de cli.commande (ListerPassages, Importer, ExporterLot, ExporterVu) déclarant son nom,
ses @Option (types convertis automatiquement) et son aide. La commande racine
CommandeRacine
liste les sous-commandes ; l'aide, l'usage et la liste des commandes sont générés par picocli (plus de
texte d'aide maintenu à la main). Les commandes restent des façades : aucune logique propre, elles
appellent les services.
- Instanciation par Guice :
FabriqueGuice(uneIFactorypicocli) fait construire chaque commande par l'injecteur, pour que ses services@Injectsoient fournis ; picocli renseigne ensuite les champs@Option. Le module étant unopen module, aucunopens ... to info.picoclin'est nécessaire. - Migration :
Cli.executermigre la base (idempotent) avant d'exécuter une sous-commande (pas pour l'aide seule), via uneIExecutionStrategy. - Sortie
--json: convention uniforme pour les commandes de lecture (scriptabilité), sérialisée parFormatJson(écrivain JSON minimal, sans dépendance supplémentaire). - Erreurs : les erreurs de parsing (commande inconnue, argument requis manquant) sont reformulées en
français et sortent en code
2; uneErreurUsagelevée dans la logique (ex. point introuvable) sort aussi en2; toute autre exception métier en1(message seul, jamais la trace).
Workspace surchargeable¶
Comme l'IHM, la CLI travaille dans un workspace (qui contient la base vigiechiro.db). L'option
globale --workspace <dir> est consommée par main() avant de bâtir l'injecteur (elle positionne
la propriété système vigiechiro.workspace, lue par CommunModule). Sans elle, le workspace par
défaut est <Documents>/VigieChiro-Companion.
Codes de sortie¶
| Code | Signification |
|---|---|
0 |
succès |
1 |
échec d'exécution (règle métier refusée, accès aux données, E/S) |
2 |
mauvaise invocation (commande inconnue, argument requis manquant ou mal formé) |
deposer-vigiechiro étend la convention : 0 seulement si le dépôt est complet ; 1 si des
fichiers restent à reprendre (relancer la même commande ne re-téléverse que les manquants). Le jeton
vient de --token, sinon de la variable d'environnement VIGIECHIRO_TOKEN, sinon de la connexion
enregistrée dans l'application (préférer la variable d'environnement : --token laisse le jeton dans
l'historique du shell).
Ces codes rendent le cycle de dépôt complet scriptable (#984). Le dépôt ne déclenche pas le traitement serveur : il faut l'appeler explicitement.
export VIGIECHIRO_TOKEN=…
vigiechiro deposer-vigiechiro --passage 9 \
&& vigiechiro lancer-traitement-vigiechiro --passage 9 \
&& vigiechiro verifier-depot-vigiechiro --passage 9 # après le calcul serveur
Le calcul serveur dure des dizaines de minutes (Tadarida tourne sur une ferme de calcul distante).
L'application ne surveille jamais la plateforme d'elle-même — le site officiel ne le fait pas
davantage — mais un script, lui, peut l'interroger à son rythme. C'est le rôle des codes de retour
d'etat-traitement-vigiechiro, le 3 signifiant « patiente » :
# attendre la fin du calcul, puis importer les observations
until vigiechiro etat-traitement-vigiechiro --passage 9; [ $? -ne 3 ]; do sleep 300; done
vigiechiro importer-vigiechiro --passage 9
En cas de dépôt à refaire de zéro (unités marquées déposées à tort), reinitialiser-depot --passage 9
efface le plan local et ramène le passage à « Prêt à déposer » — les archives et la participation sont
conservées, le dépôt suivant re-téléverse tout.
executer(...) ne fait pas System.exit (il renvoie le code) : c'est ce qui le rend testable.
Seul main() traduit le code en System.exit. La base est migrée au démarrage (idempotent) avant
toute commande, donc une première invocation crée le schéma si besoin.
Désigner les observations d'un geste de revue (#1311)¶
C'était la vraie difficulté de la parité : l'écran raisonne par sélection dans une table, la ligne
de commande n'a pas de table. Les gestes de revue offrent donc deux manières de désigner, exclusives
et obligatoires (on ne pose pas un geste sans dire sur quoi), portées par la classe partagée
CiblesRevue :
# 1. Chirurgical : sur des lignes qu'on a LUES.
./vigiechiro lister-observations --passage 3 --statut NON_TOUCHEE
./vigiechiro valider-observations --observation 12,13,14
# 2. Scripté : sur un sous-ensemble DÉCRIT, avec les MÊMES filtres que lister-observations.
./vigiechiro corriger-observations --taxon Pippip --passage 3 --taxon-tadarida Pipkuh
La garantie : c'est le même SelectionObservations qui choisit pour lister et pour agir. Ce que
lister-observations --passage 3 --statut NON_TOUCHEE montre est exactement ce que
valider-observations --passage 3 --statut NON_TOUCHEE touche. On regarde, puis on agit - et c'est
mécanique, pas promis.
Viser un passage sans filtre exige --confirmer
--passage 3 seul, c'est toutes les observations du passage - des centaines. Un filtre oublié
dans un script transforme « corrige ces trois lignes » en « corrige la nuit entière », et rien dans
la commande ne distinguerait l'un de l'autre. Ce cas exige donc --confirmer.
Et une sélection qui ne retient rien lève, au lieu de répondre « 0 observation traitée » : un geste qui ne touche rien est presque toujours une erreur de filtre.
Les filtres booléens (--douteux, --reference) sont ternaires : posés, ils ne gardent que les lignes
concernées ; absents, ils laissent passer les deux - ils ne veulent pas dire « seulement les
non-douteuses ».
Lancer la CLI¶
Il n'y a pas encore de lanceur empaqueté : on l'exécute via exec-maven-plugin (même mécanique que le
banc de performance), avec le JDK 25 standard (comme la CI) :
export JAVA_HOME=~/.sdkman/candidates/java/25.0.2-open
./mvnw -q -DskipTests compile
./mvnw -q org.codehaus.mojo:exec-maven-plugin:exec \
-Dexec.executable="$JAVA_HOME/bin/java" -Dexec.classpathScope=runtime \
-Dexec.args="-cp %classpath fr.univ_amu.iut.cli.Cli --workspace /tmp/vigiechiro-cli lister-passages"
Cli.main(String[]) existe et reste le point d'entrée naturel pour un futur lanceur natif (jpackage).
Tests¶
La CLI est couverte par
CliTest
(dispatch, codes de sortie, aide),
CliImportTest
et
CliExportVuTest.
Ils positionnent vigiechiro.workspace sur un @TempDir et capturent les flux sortie/erreur :
aucun JavaFX, donc des tests rapides et déterministes.