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 ..] [--sans-verification] |
A10 | ServiceSites.creerSite |
recuperer-carre |
--carre <n> [--nom ..] [--protocole ..] [--commentaire ..] |
A10 | RapatriementCarre.rapatrier |
situer-carre |
--position "<lat, lon>" |
A10 | PropositionCarre.pour. Aucun réseau : le carroyage national est embarqué (137 479 mailles, métropole), et c'est ce qui rend la commande utile à un script - traiter un lot de nuits dont on a les positions GPS sans ouvrir l'application. Trois issues distinguées par le code de sortie, pour qu'un script tranche sans lire la prose : un carré unique s'écrit sur la sortie standard (exit 0) ; sur une frontière les candidats s'y écrivent tous et le motif part sur l'erreur (exit 1), parce que deux centres à distance égale ne se départagent pas ; hors métropole rien ne s'écrit (exit 1) ; un texte illisible rend un code d'usage (exit 2), qui n'est pas la même chose qu'une position sans carré |
modifier-site |
--site <id> --carre <n> [--nom ..] [--protocole ..] [--commentaire ..] |
A10 | ServiceSites.modifierSite |
supprimer-site |
--site <id> [--confirmer] |
A10 | ServiceSites.supprimerSite |
ajouter-point |
--site <id> --code <c> [--lat ..] [--lon ..] [--description ..] |
A10 | ServiceSites.ajouterPoint. Avec une position, contrôle le carré du site hors ligne (#4671) : une divergence part sur stderr, l'identifiant du point reste seul sur stdout et le code de sortie ne change pas - POINT=$(vigiechiro ajouter-point ...) continue de marcher |
modifier-point |
--point <id> --site <id> --code <c> [--lat ..] [--lon ..] [--description ..] |
A10 | ServiceSites.modifierPoint. Même contrôle qu'ajouter-point : le défaut se pose aussi bien en déplaçant qu'en créant |
lister-sites |
[--json] |
A10 | ServiceSites (lecture) |
lister-sites-vigiechiro |
[--portee mes\|plateforme] [--pages <n> \| --tout] [--point <code>] [--carre <n>] [--recenser] [--json] [--token <jeton>] |
#3003 | ClientVigieChiro.sitesPlateforme / ClientVigieChiro.mesSites / ClientVigieChiro.chercherCarre. Interroge le catalogue de la plateforme (20 767 sites au 2026-08-14) ou vos sites. --recenser compte les sites par code de point : c'est ainsi qu'on établit qu'un code comme Z1 est porté par des centaines de carrés (#2993). Chaque sortie porte son dénominateur (« 300 lus sur 20517 annoncés, 3 pages sur 206 ») et signale les sites sans point ponctuel (transects routiers, dont les localités sont des lignes) : un échantillon ne doit jamais passer pour un recensement. --carre fait exception depuis #3769 : il est porté par le serveur (q=<numéro>, filtre mesuré le 2026-08-14 - 20 767 → 1), donc en une requête au lieu de deux cents pages. Le gain n'est pas d'abord la vitesse : sans --tout, la commande lisait une page sur 208 et rendait un tableau vide sur un carré qui existe. Sa sortie annonce alors une recherche (« Recherche du carré 130711 sur toute la collection : 1 site(s) trouvé(s) ») et non une lecture partielle, et --pages / --tout sont refusés avec lui (exit 2) plutôt qu'acceptés sans effet. Les autres filtres (--point) restent appliqués chez nous, ce backend ignorant where= en silence |
lister-participations-vigiechiro |
[--json] [--token <jeton>] |
#3005 | ClientVigieChiro.mesParticipations. Vos nuits déposées, avec leur identifiant : c'est lui que réclament importer-vigiechiro --participation et reconstruire-passage --participation, et qu'aucune commande ne donnait. reconstruire-passage sans argument ne liste que les participations orphelines ; une nuit déjà rattachée n'apparaissait nulle part |
api |
lire --chemin <chemin> [--page <n>], ressources [--sonder] |
#3006 | Interrogation brute de l'API, en lecture seule. Groupe volontairement discret : ses sous-commandes ne sont pas détaillées ici (elles parlent le langage de l'API, pas celui du produit) - le détail vit dans api-vigiechiro.md. lire refuse avant d'émettre les deux pièges qui ne préviennent pas (max_results au-delà de 100, where= que le serveur ignore) ; ressources affiche la carte des lectures et sait la confronter au serveur. Aucune écriture : ADR 3006 |
creer-campagne |
--nom <n> [--annee N] [--commentaire ..] |
#2355 | ServiceCampagne.creerCampagne : crée une campagne (regroupement facultatif de passages). Année par défaut = année courante. Feature campagne (désactivable) |
lister-campagnes |
[--json] |
#2355 | ServiceCampagne (lecture) : les campagnes de suivi, de la plus récente à la plus ancienne |
modifier-campagne |
--campagne <id> --nom <n> --annee N [--commentaire ..] |
#2355 | ServiceCampagne.modifierCampagne : corrige nom, année et commentaire. Sans elle, corriger une campagne obligeait à la supprimer, donc à détacher tous ses passages |
rattacher-campagne |
--passage <id> [--campagne <id>] |
#2355 | ServiceCampagne.rattacherPassage : rattache un passage à une campagne, ou l'en détache si --campagne est omis. Supprimer la campagne détache aussi (ON DELETE SET NULL) |
supprimer-campagne |
--campagne <id> |
#2355 | ServiceCampagne.supprimerCampagne : supprime le regroupement, jamais les nuits, les passages rattachés sont détachés (ON DELETE SET NULL) |
lister-passages |
[--carre <n>] [--lieu <texte>…] [--annee <a>] [--statut <s>] [--verdict <v>] [--analyse <e>] [--campagne <nom>] [--json] |
P5, #3269 | RegistrePassages (lecture). Les sept filtres sont ceux de l'écran « Carte & passages » et passent par le même prédicat (FiltresMultisite, un record dont accepte ignore les critères non renseignés) ; --lieu lit FiltresLieu, comme lister-observations. Aucune règle n'est réécrite. --analyse porte un état déduit (EtatAnalyse.deduire) et non lu : « Déposé » sans relevé vaut JAMAIS_RELEVE, « Importé » vaut SANS_OBJET. Une base sans aucun passage le dit avant tout filtrage : sinon --lieu refuserait le lieu (ADR 3082) là où la vérité est qu'il n'y a rien. Un filtre qui ne retient rien le dit aussi |
solde-saison |
[--annee N] [--campagne <nom>] [--lieu <texte>] [--reste-a-faire] [--format texte\|csv\|json] |
#2356 | ServiceSoldeSaison (lecture) : ce qu'il reste à faire, point par point, pour une saison (règles R3/R4 restituées). --lieu et --reste-a-faire sont les deux filtres de l'écran « Ma saison » (#3103), portés ici à la clôture de #3092 et lus sur la même écriture (FiltresSaison). Ils ne touchent que la liste des points : l'en-tête continue d'annoncer le solde de la saison entière, comme à l'écran. Un filtre qui ne retient rien le dit. Même décompte que l'écran « Ma saison » (service partagé, parité IHM/CLI). texte par défaut, csv/json scriptables. Les trois sorties montrent le nom du carré (#3289), par lequel --lieu sait déjà chercher : le texte le qualifie (640380 · Vallon), le CSV a sa colonne « Nom du carré », le JSON sa clé nom_site. L'écran, lui, lui donne une colonne : qualifié dans « Carré », le nom s'y faisait tronquer. La commune suit le même chemin (#3313) : colonne à l'écran, Commune au CSV, commune en JSON, entre parenthèses en texte, et --lieu la retient |
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). La cohérence horaire dit le protocole (#4984) : la fenêtre exigée va de 30 minutes avant le coucher à 30 minutes après le lever, c'est un plancher, et la dépasser n'est pas un défaut. Le texte cite les deux plages - protocole 21:28 à 06:23, enregistré 22:00 à 05:00 - et le JSON porte debutExige, finExigee, debutEnregistre, finEnregistree plus couvertureDuProtocole. Cette dernière vaut COUVERTE, INCOMPLETE ou INDISPONIBLE : elle nomme l'état du domaine, pas la gravité de son annonce, laquelle est une décision de surface (ADR 0038). Un script qui voudrait alerter teste INCOMPLETE, et jamais l'absence de COUVERTE, INDISPONIBLE ne signifiant pas « mauvais » mais « on n'a pas pu savoir » |
importer |
--source <chemin> --point <id> [--annee N] [--passage N] [--conserver-originaux \| --sans-originaux] [--ecraser] |
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). Collision de numéro (#2278) : la perte est chiffrée (séquences, validations Tadarida) et, sans --ecraser, rien n'est importé, sortie 2. Avec, ServiceImport.ecraserEtImporter sauvegarde d'abord, puis remplace la nuit existante. Parité #2350 : la sortie dit aussi les volumes lus / écrits (#2358, les mêmes chiffres que la bande de l'écran, sans les barres) et la participation Vigie-Chiro créée (#1488), une écriture distante ne se découvre pas ailleurs. Parité #3195 : --source accepte aussi une archive .zip, décompressée sous le dossier de travail (jamais sous /tmp, souvent un tmpfs en RAM) puis effacée, refus compris ; les bornes de ressources de #2732 valent donc aussi pour la ligne de commande, et un refus y sort en 2 |
importer-transformes |
--dossier <dir> --point <id> [--annee N] [--passage N] [--referencer] |
#2433 (EPIC #2258) | ServiceImportReference. Crée un passage à partir d'un dossier de séquences déjà transformées, sans rejouer la transformation : l'original est un placeholder (pas de brut), l'identité (empreinte + taille) est calculée à l'inscription pour que la réactivation la revérifie au réveil. Par défaut copie les WAV dans l'espace de travail ; --referencer les laisse en place, la base pointe l'emplacement externe et aucun octet audio n'est recopié (ADR 2433). Série et date déduites des noms de fichiers. Refus métier (dossier introuvable, aucun WAV, point inconnu, quadruplet déjà pris) en sortie 2, rien n'est créé, l'état est intact |
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é), et les séquences illisibles que la jointure a dû écarter (sequencesIllisibles en JSON, #4739). 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 » |
emporter-nuit |
--passage <id> --vers <fichier> [--oui] |
#4729 | Écrit le paquet d'une nuit pour la faire relire ailleurs. Annonce le volume et s'arrête sans --oui : on n'emporte pas des gigaoctets sur un script lancé de travers. Parité CLI de « Emporter cette nuit… » |
ouvrir-paquet-recu |
--fichier <paquet> |
#4729 | Le relecteur ouvre un paquet confié : la sélection de l'expéditeur devient la sienne, figée. L'identité s'appose à l'ouverture (#4626), et son absence est un refus |
renvoyer-avis |
--passage <id> --vers <fichier> |
#4729 | Le relecteur renvoie son jugement : un manifeste signé de lui, sans séquence, l'expéditeur les ayant déjà |
reprendre-avis |
--fichier <paquet> [--remplacer] |
#4729 | Range l'avis revenu à côté du nôtre, jamais dessus (ADR 4517). Un second avis exige --remplacer : en ligne de commande, aucune modale ne rattrape un écrasement |
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 = ChoixSourceDepot.pour, 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. Parité #2350 : depuis le réessai gradué (#2354) une coupure momentanée n'échoue plus, elle attend ; la ligne ~ <unité>, nouvelle tentative dans N s le dit, là où le silence faisait passer la temporisation pour un blocage |
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 si le serveur a refusé la relance, 2 pour un refus métier en amont (dépôt indisponible, aucune participation liée au passage, rendu par le handler central, #2294). 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, 1 en échec côté serveur, 4 jamais lancé, 2 indisponible (on n'a pas pu demander : nuit non déposée, jeton absent, plateforme injoignable, refus métier rendu par le handler central, #2294) |
traiter-passages |
--action <preparer-depot\|televerser\|importer-resultats\|declencher-calcul> --passage <id> [--passage <id>…] [--json] |
#2357 | MoteurTraitementGroupe sur les quatre ActionGroupee, équivalent du menu « Traiter la sélection » de « Carte & passages ». Ce qu'une boucle shell ne sait pas faire : savoir à l'avance lesquels sont éligibles (les règles lisent l'état du dépôt, le rattachement et la présence de résultats, rien de tout cela n'étant dans lister-passages). Les écartés sont annoncés avec leur motif sans être tentés, un échec n'arrête pas les suivants, chaque passage a sa ligne. Ni --forcer ni --remplacer : les actions groupées ne les exposent pas, un recalcul détruisant les observations d'un dépôt en archives et un remplacement touchant à ce que l'observateur a validé, vingt d'un coup ne serait pas un service. 0 si aucun échec (les écartés en font partie : rejouer un lot déjà traité est le cas idempotent, comme lancer-traitement-vigiechiro sur « déjà en cours »), 1 si au moins un passage a échoué, 2 pour un refus en amont (fonctionnalité désactivée, identifiant inconnu) |
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 » |
supprimer-passage |
--passage <id> [--confirmer] |
#2278 | ServicePassage.supprimer, équivalent du bouton « Supprimer » de la fiche passage. Destructif : chiffre d'abord la perte (séquences, validations Tadarida menacées) puis, sans --confirmer, ne touche à rien et sort en 2. Un passage déposé ou introuvable est refusé par le métier (code 2 : état intact). La cascade s'arrête à la base : les fichiers de la nuit restent sur le disque |
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 au moins un envoi est refusé par le serveur (échec partiel de publication), code 2 si la publication est indisponible dans ce contexte, fonctionnalité désactivée (refus métier, état intact)). 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 |
reactiver |
--passage <id> --source <dir> [--referencer] [--json] |
#1302, #1406, #1571, #2255 | 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. Avec --referencer (#2255), rien n'est copié : la base pointe les fichiers là où ils vivent (NAS, disque externe, dossier de travail). La nuit devient muette si ce support n'est plus joignable et redevient écoutable quand il revient, l'identité étant revérifiée (#2254). Les tranches régénérées depuis les bruts restent copiées : elles sont produites par l'application, pas par l'utilisateur |
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) |
rattraper-communes |
(aucune option) | #2791 | ServiceCommunes : comble la commune des points en attente (GPS présent, commune absente) via l'API Géo. Rejouable sans risque ; hors ligne, les points restent en attente |
exporter-vu |
--passage <id> --sortie <fichier> |
P7 | ServiceValidation |
exporter-observations |
--passage <id> --sortie <fichier> |
#149 | ProjectionsAudioDao.lignesAudioDuPassage + ExportObservationsCsv |
exporter-sons |
(--passage <id> \| --espece <code>) --sortie <zip> [--lieu <lieu>]… [--proba-min <0..1>] |
#2795 | ProjectionsAudioDao.lignesAudioDuPassage / lignesAudioDeLEspece + ExportObservationsEtSons (#2792). Parité CLI du geste « Exporter les observations et les sons » de la vue audio (#2793) : archive observations.csv + sons/<session>/<fichier>, sons introuvables comptés sans bloquer. --passage couvre le même sous-ensemble qu'exporter-observations (CSV identique) et refuse un passage inconnu (code 2) ; --espece couvre l'espèce sur tous les passages de l'utilisateur, tous statuts, et une espèce sans observation produit une archive au CSV d'en-têtes seuls |
exporter-activite |
(--passage <id> \| --tout) --sortie <fichier> [--tranche 15\|30\|60] [--format csv] |
#2352 | ServiceActivite.contactsDuPassage + AgregationActivite.parEspece + ExportActiviteCsv. Facette données de la courbe d'activité (pendant CLI de l'export image de l'IHM), rattachée à la nuit biologique. Une ligne par (carré, point, nuit, espèce, tranche) : chaque ligne porte son lieu, sans quoi un export couvrant plusieurs nuits ne se recouperait pas (#2613). --tout couvre la vue transverse de l'écran. Non gouvernée par la fonctionnalité activite-nuit : l'agrégation est une capacité stable de analyse, celle-ci ne gouvernant que l'accès à la vue |
audit-coherence |
[--passage <id>] [--gravite <niveau>] [--categorie <nature>] [--contient <texte>] [--json] [--online] [--token <jeton>] |
#1133, #1254, #1347 | ServiceAuditCoherence : confronte disque, base et serveur. --gravite et --categorie sont les deux puces de l'écran « Audit de cohérence » (#3100), portées ici à la clôture de #3092 ; chacune retient une valeur exacte. Elles filtrent l'affichage seul : le code de sortie continue de juger le rapport entier, sans quoi --gravite INFO sur un workspace abîmé rendrait 0 et un script d'intégration conclurait que tout va bien. 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 |
<chemin> [--complet] --confirmer |
#148, #1346 | ServiceSauvegarde.restaurer / restaurerComplet : remet la base (et, avec --complet, les dossiers de son, remis là où ils étaient, avec les root_path corrigés et un compte rendu de ce qui a changé de place, #2727). Écrase l'état local : --confirmer est obligatoire. La base courante est mise de côté (vigiechiro.db.avant-restauration). Trois codes : 0 restauration entière, 10 restaurée mais il manque quelque chose, 2 refus (#3500) |
lister-sauvegardes |
[--dossier <d>] [--json] |
#3197 | InventaireSauvegardes.lire. Ce que sauvegardes/ contient : nom, date, taille, et le total. L'application y écrit un filet complet avant chaque migration de schéma et n'en supprime jamais aucun - délibérément (ADR 0048 : le filet appartient à l'utilisateur), mais jusqu'ici sans que rien ne dise combien il y en avait ni ce qu'ils pesaient. La commande observe : elle ne purge rien et ne conseille rien. Les sauvegardes complètes étant des dossiers, leur taille est celle de leur contenu - un inventaire qui ne verrait que les fichiers mentirait là où le chiffre compte |
supprimer-sauvegarde |
--nom <nom> [--dossier <d>] --confirmer |
#3197 | Le ménage que la doc conseille, explicitement demandé. Même parade que supprimer-passage : sans --confirmer, la commande chiffre la perte et ne touche à rien, sortie 2. Un nom inconnu est une erreur d'usage, pas un succès silencieux : croire avoir supprimé ce qu'on n'a pas touché est précisément ce qu'on veut éviter |
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 |
synthetiser-passage |
--passage <id> [--carre ..] [--milieu ..] [--validees-seulement] [--format csv\|json] [--sortie <f>] |
#2351 | Facette CLI de M-Synthese. Contacts par espèce et classe d'activité au regard du référentiel ACTICHIRO, avec les quantiles retenus. Même ServiceSynthese que l'écran : mêmes règles de repli, même résultat, une synthèse qui différerait d'une surface à l'autre serait pire qu'absente. Le contexte, l'avertissement et la citation sont écrits en tête du CSV (lignes #) et dans un objet contexte en JSON : la source est libre d'usage avec citation obligatoire, et un avertissement resté à l'écran ne prévient personne qui ouvre le fichier |
lister-observations |
--passage <id> [--statut ..] [--taxon ..] [--douteux] [--reference] [--a-enjeu] [--certitude ..] [--json] [--lieu <lieu>]… [--proba-min <0..1>] |
#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. --a-enjeu (#2353) ne garde que les espèces prioritaires du Plan National d'Actions Chiroptères, et le drapeau enjeu les signale dans la sortie (champ aEnjeu en JSON) |
lister-especes |
[--statut <s>] [--taxon-parent <t>] [--lieu <texte>…] [--nature protocole\|opportuniste] [--a-enjeu] [--format csv\|json] [--sortie <f>] |
#3269 | Facette CLI de l'inventaire par espèce d'« Espèces & observations ». Même AgregationAnalyse.parEspece que l'écran et mêmes colonnes que son export : le tableau ne peut pas dire une chose ici et une autre là. Les cinq critères sont ceux de la barre à puces, mais posés en fragments tapés (partiels, insensibles à la casse et aux accents) là où l'écran fait cocher dans une liste : c'est l'écart assumé de FiltresAnalyse avec le catalogue de la vue. Le point n'est pas filtrable (un code seul désigne autant de lieux qu'il y a de carrés). Une base sans aucune observation se constate avant tout filtrage : sinon --lieu refuserait le lieu (ADR 3082) là où la vérité est qu'il n'y a rien |
lister-carres |
[mêmes filtres que lister-especes] [--format csv\|json] [--sortie <f>] |
#3269 | Facette CLI de l'inventaire par carré du même écran : la richesse en espèces et les détections, par carré prospecté. Répond à « où » quand sa jumelle répond à « quoi » - deux commandes plutôt qu'une à --regrouper, parce que leurs colonnes n'ont rien en commun et qu'un script ne doit pas avoir à deviner celles qu'il recevra. La richesse suit la sélection, comme à l'écran : --a-enjeu donne la richesse en espèces prioritaires, --statut VALIDEE celle qu'on peut défendre |
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é |
emplacements |
[--definir-travail <dir>] [--definir-base <dir>] [--reinitialiser] [--json] |
#1038 | Parité CLI de l'onglet « Emplacements » (ADR 1038) : ServiceEmplacements. Sans option, affiche où vivent le dossier de travail, la base et les journaux (et leurs défauts). Les journaux y figurent parce que la CLI n'imprime plus la pile d'un incident : le détail n'existe qu'une fois, dans ce dossier, et l'IHM est seule à savoir l'ouvrir (#3624). --definir-* sonde chaque dossier (un fichier ou un dossier non inscriptible est refusé, code 2 : rien n'est écrit) puis écrit le choix ; --reinitialiser l'efface. Ne déplace rien : change le pointeur lu au prochain démarrage, pas les données - une base pointée vers un dossier vide démarre neuve. --reinitialiser et --definir-* sont exclusifs (code 2) |
--help / -h, --version / -V, ou aucun argument |
— | — | — |
La couleur est choisie, pas déduite (#3738)¶
Cli pose explicitement le mode ANSI à la construction du CommandLine. Sans cela, picocli applique
son heuristique AUTO, qui ne décide pas la même chose partout : la première exécution de la suite
sous Windows a rendu une aide colorisée là où Linux la rend nue - Usage: ESC[1mvigiechiro….
La règle : de la couleur pour un humain devant un terminal (System.console() != null), jamais
dans un tuyau, un fichier ou un journal de CI ; et l'utilisateur a le dernier mot dans les deux
sens :
| Variable | Effet | Convention |
|---|---|---|
NO_COLOR |
éteint, toujours | spécifiée |
FORCE_COLOR |
allume, même sans console | usage répandu, non spécifié |
Les deux comptent dès qu'elles sont présentes et non vides, quelle que soit leur valeur - une seule
lecture à retenir pour deux variables voisines. NO_COLOR l'emporte quand les deux sont posées :
un refus explicite prime sur une demande explicite. Se tromper dans ce sens affiche du texte nu ; se
tromper dans l'autre crache des séquences d'échappement chez quelqu'un qui a demandé qu'on ne le fasse
pas.
FORCE_COLOR a été ajoutée parce que la règle précédente ne donnait le dernier mot que dans un
sens (#3796). Trois situations ordinaires en souffraient, et elles ont un point commun : la sortie est
redirigée alors qu'un humain la lit - … | less -R, un journal de CI qui interprète l'ANSI (les
Actions GitHub le font), un enrobage script/unbuffer. Dans les trois, l'utilisateur veut la couleur,
la console sait l'afficher, et le produit refusait.
La décision vit dans CouleurCli, sur des entrées fournies plutôt que lues du système : la
console et l'environnement d'une JVM en cours ne se manipulent pas de façon portable, et c'est
précisément cette non-portabilité qui avait créé le défaut. cli.bats garde par ailleurs les
quatre cas sur la sortie réelle du binaire, dont deux qui n'existent que là : la couleur s'allume
devant un vrai terminal, et FORCE_COLOR l'allume même redirigée.
Il faut un vrai pseudo-terminal (script -qec) pour les éprouver. Sans lui, deux cas sur quatre
sont indiscernables : sous une sortie redirigée, l'aide est nue de toute façon, donc « NO_COLOR
éteint » y serait vert sans rien prouver.
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; un refus métier (RegleMetierExceptionou l'IllegalArgumentExceptiondes validateurs) sort en2(état intact) ; toute autre exception (échec inattendu, état incertain) en1(message seul, jamais la trace).
« Jamais la trace » n'était vrai qu'à moitié jusqu'à #3570, et de deux façons. D'abord
Cli.main amorce la journalisation et migre la base avant d'entrer dans la commande, donc hors du
gestionnaire de picocli : une exception née là sortait par la JVM, Exception in thread "main" et pile
complète, en code 1 - y compris un RefusAvantEcriture que #3498 avait pourtant appris à traduire en
2. Ensuite, la JVM installe d'office un ConsoleHandler que personne ne retirait : tout incident
reversait donc sa pile sur la sortie d'erreur, en plus de la phrase.
Les deux sont corrigés : le classement exception vers code vit dans VerdictCli, appelable des deux
côtés, et la CLI retire la console au démarrage (ConfigurationJournalisation.configurerSansConsole).
L'IHM garde la sienne : le trajet des journaux est un choix de surface, et personne ne script la sortie
d'une fenêtre. La trace n'est pas perdue, elle est dans <workspace>/logs/.
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.
Relever une borne : --reglage <cle>=<valeur> (#4075)¶
Sept bornes protègent la lecture d'une entrée externe : taille d'un fichier de la carte, corps d'une
réponse, contenu d'une archive, hôtes admis pour un lien signé. Elles se surchargeaient par propriété
JVM, et trois refus le conseillaient : « relancez avec -Dvigiechiro.… ».
Ce conseil était inapplicable pour qui installe le produit. Le lanceur jpackage passe ses
arguments à main, jamais à la machine virtuelle : -D n'y existe pas. La seule issue chez
l'utilisateur était de renoncer au fichier, ce que le doc-comment de PlafondLecture voulait
précisément éviter.
L'option est répétable, consommée par main() avant l'injecteur - même trajet que --workspace,
et pour la même raison : les bornes sont lues à la construction des services.
Les clés vivent dans le registre CleDeReglage, et rien d'autre ne les liste : une clé inconnue
nomme celles qui existent, ce qui met la liste là où on en a besoin. Le registre est aussi ce qui rend
l'option sûre - elle écrit une propriété système, donc sans lui elle en écrirait n'importe laquelle, y
compris celles de la plateforme.
Ce que cette option ne fait pas
Elle ne rend pas ces bornes réglables : il n'y a toujours pas d'entrée dans l'écran Réglages, et cette décision tient - un naturaliste n'a pas à choisir une taille de corps de réponse. Ce qui change est qu'une limite atteinte nomme une porte de sortie qui existe là où l'utilisateur se trouve. Le message est le même à l'écran et au terminal : la ligne de commande est le seul endroit où ces bornes se relèvent, et inventer une consigne propre à l'écran promettrait un geste absent.
Le dossier de travail est réservé pendant l'écriture (#3498)¶
L'application graphique réserve le dossier de travail pour toute sa durée (VerrouWorkspace,
2731) ; la CLI ne le demandait jamais et écrivait donc par-dessus, alors que la doc du verrou nomme¶
elle-même le cas : « deux instances graphiques, une IHM et une CLI, ou une restauration pendant un
import ». Depuis #3498, Cli.migrerPuisExecuter prend le verrou par défaut, pour toute la durée
de la commande. Un dossier déjà occupé donne un refus : code 2, état intact.
Une commande s'en dispense en portant l'interface marqueur fr.univ_amu.iut.cli.LectureSeule. La
déclaration porte sur les lectrices, pas sur les écrivaines, et le sens compte : oublier de déclarer
une écrivaine laisserait son écriture échapper au verrou, en silence ; oublier une lectrice fait
refuser une consultation pendant que l'application est ouverte, ce qui se voit et se signale le jour
même. « Lecture seule » veut dire : ne touche ni la base ni les dossiers de session.
Interroger le réseau, ou écrire hors du dossier de travail (lister-carres --sortie,
synthetiser-passage --sortie), reste de la lecture seule - tout comme écrire la configuration
d'amorçage, qui vit ailleurs et s'écrit d'un seul coup (#3507). C'est pourquoi emplacements est
lectrice : elle sert à repointer le dossier de travail, et la verrouiller refuserait de déménager
à qui déménage parce que la place actuelle est occupée.
Le journal fait exception. Cli.main amorce la journalisation avant tout, donc toute
commande crée et écrit <dossier de travail>/logs - les lectrices comprises. C'est voulu : un incident
doit laisser une trace même sur une commande qui ne fait que lire, et deux processus qui écrivent
chacun ses lignes dans un journal ne se corrompent pas, là où deux processus qui écrivent la même base
le feraient (#3575).
C'est aussi pourquoi `emplacements` **nomme** ce dossier : la trace retirée de la console doit
rester trouvable sans le deviner. `cli.bats` le vérifie des deux bouts - la pile est dans le
fichier, et la commande désigne le dossier réellement écrit.
La migration du schéma prend le verrou de son côté : une commande de lecture sur une base à mettre à jour peut donc être refusée, et c'est voulu - mettre à jour le schéma est une écriture.
ClassementLectureEcritureTest exige que chaque commande soit classée : elle porte le marqueur,
ou elle figure dans la liste des écrivaines, tenue dans ce test. Le garde ne juge pas le classement -
aucune analyse statique n'y arrive - il rend l'oubli impossible.
Codes de sortie¶
| Code | Signification |
|---|---|
0 |
succès |
1 |
échec d'exécution : accès aux données, E/S, incident inattendu (état incertain) |
2 |
mauvaise invocation (commande inconnue, argument requis manquant ou mal formé) ou refus (règle métier, ou garde destructive : état intact, rien n'a été fait) |
10 |
succès partiel, sur les seules commandes qui le documentent (verifier-maj, restaurer) : l'opération a abouti, mais quelque chose reste à regarder |
2 dit aussi « j'ai refusé, je n'ai rien fait ». Les commandes destructives exigent un drapeau
explicite (--confirmer, --ecraser) : sans lui, elles chiffrent la perte et sortent en 2 sans
rien toucher. C'est volontairement distinct de 1 : après un 1, l'état est incertain ; après un
2, il est intact, et un script peut s'arrêter sans avoir à vérifier quoi que ce soit. Le message
de refus part sur stderr, pour ne pas se mêler au compte rendu.
Suivent cette règle : supprimer-passage, importer --ecraser, restaurer, reset-guide --executer,
discussion, sauvegarder (incomplète), emplacements (dossier refusé par la sonde, options
exclusives : rien n'est écrit). restaurer rendait 1 sur stdout jusqu'à #2294, la
convention n'était écrite nulle part, et c'est ainsi qu'elle a dérivé.
Les refus de la couche persistance en font partie depuis #3146 : un fichier qui n'est pas une
base, une sauvegarde écrite par une version plus récente, un manifeste abîmé, un dossier de travail
occupé. Tous sont émis avant la moindre écriture, et sortaient pourtant en 1, avec une pile.
Ils portent maintenant RefusAvantEcriture, que le gestionnaire d'erreurs classe avec les autres
refus. Une DataAccessException ordinaire, elle, reste un incident : sa pile est l'information
utile.
Un troisième code quand « réussi » ne suffit pas (#3500). restaurer --complet imprimait qu'une
nuit manquait, puis sortait en 0 : le fait était dit dans un texte qu'un script ne lit pas. Elle rend
maintenant 10 quand la restauration laisse un manque - la sauvegarde ne dit pas d'où venaient
les dossiers, ou une nuit connue de la base n'y était pas.
Pas 1 ni 2 : la restauration a réussi. Confondre « ça n'a pas marché » avec « ça a marché,
regarde quand même » est exactement ce que verifier-maj a payé avant elle.
Et pas sur les dossiers simplement replacés ailleurs, qui restent 0 : c'est le cas normal
d'une restauration sur une autre machine, l'usage principal de --complet, et le compte rendu nomme
déjà l'ancienne et la nouvelle adresse. Un 10 permanent s'apprend à s'ignorer, et emporterait avec
lui celui qui compte. L'IHM, elle, garde son avertissement sur les trois cas : elle parle à quelqu'un
qui lit le compte rendu (ADR 3500).
deposer-vigiechiro marque d'un ~ ce que la réconciliation n'a pas pu vérifier (#4631) : aucune
unité n'a échoué, c'est l'étape d'avant qui n'a pas tourné, et des archives déjà en ligne vont
repartir. Un script distingue donc ! (une unité a échoué) de ~ (on n'a pas pu savoir).
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.
--proba-min : l'échelle, et la détection sans probabilité
Garde les détections dont la probabilité Tadarida atteint le seuil, et conserve celles qui n'en ont pas : une absence de probabilité n'est pas une mauvaise probabilité, et l'écarter perdrait précisément une ligne à revoir. C'est la règle de la puce « Proba », reprise telle quelle.
L'échelle est 0 à 1, pas le pourcentage de l'écran. lister-observations imprime déjà
probTadarida brut (0.74) : entrée et sortie d'un même appel parlent ainsi la même langue.
--proba-min 90, réflexe du pourcentage, est refusé (code 2) avec l'unité rappelée, plutôt que
borné en silence, ce qui rendrait zéro ligne sans dire pourquoi.
Un résultat vide n'est pas un refus ici, contrairement à --lieu : un seuil est un nombre, il
ne peut pas désigner ce qui n'existe pas, et « aucune détection au-dessus de 0,99 » est une réponse.
Mais une réponse muette : c'est le seul filtre qui puisse légitimement tout écarter sans rien dire de ce qu'on a raté. Quand le seuil vide le lot, la commande nomme donc la meilleure probabilité présente (« la plus sûre du lot est à 0,74 : abaissez le seuil pour l'atteindre »), ce qui apprend du même coup que le lot n'était pas vide et de combien descendre.
--lieu : ce qu'il couvre, et ce qu'il ne couvre pas
Répétable, il retient les observations dont la commune ou le carré correspond, en
correspondance partielle et insensible à la casse comme aux accents : --lieu aix trouve
« Aix-en-Provence ». À l'écran on coche dans une liste fermée, en ligne de commande on tape à
l'aveugle, sans rien pour rappeler l'orthographe.
Le nom convivial du carré n'est pas une valeur de plus : c'est l'autre étiquette du même lieu
(ADR 3157). --lieu 640380 et
--lieu vallon retiennent donc le même carré, et le refus le nomme d'un seul tenant,
« 640380 · Vallon », comme l'écran l'affiche - pour que la valeur suggérée se recopie telle quelle.
Personne n'a pour autant à taper le point médian.
Le point n'en fait pas partie, contrairement à la puce « Lieu » de l'écran. Le schéma pose
UNIQUE(site_id, code) : un code seul (« A1 », « Z1 ») désigne autant de lieux qu'il y a de carrés.
L'écran s'en tire en l'affichant qualifié (« 640380 · A1 »), ce qui suppose une liste sous les yeux ;
--lieu A1 rouvrirait le défaut sans que rien ne le montre. Le point restera atteignable par un
croisement --carre / --point.
Un lieu sans correspondance est un refus (code 2) qui nomme les lieux présents, jamais une archive vide en code 0 : un script enchaînerait sans voir la faute de frappe, et l'expert recevrait une archive creuse.
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¶
Depuis un produit installé¶
Le lanceur des emballages est un point d'entrée unique : le mot ihm ouvre la fenêtre, tout le
reste part à la ligne de commande (#4071). Personne n'a à taper ihm pour ouvrir l'interface :
chaque emballage l'écrit pour son double-clic - jpackage --arguments ihm le dépose dans le .cfg du
lanceur, le script Flatpak porte le même défaut.
La commande s'appelle vigiechiro, comme l'annonce déjà l'aide de picocli. C'est une seconde
enveloppe posée par --add-launcher sur la même classe principale, et non un second point d'entrée :
VigieChiroCompanion lister-passages répond exactement pareil. Elle existe pour deux raisons, dont
une seule est technique - sous Windows, elle porte la console que le lanceur graphique ne peut pas
avoir (ADR 4071) ; ailleurs, elle donne simplement à la commande le nom qu'on tape.
# paquet Debian installé : la commande est dans le PATH
vigiechiro lister-passages
# archive portable Linux (et app-image)
bin/vigiechiro lister-passages
# .msi et archive portable Windows
bin\vigiechiro.exe lister-passages
# Flatpak
flatpak run fr.echonuit.VigieChiroCompanion lister-passages
# AppImage
./VigieChiroCompanion-2.185.0-linux-x86_64.AppImage lister-passages
Sous Flatpak, --workspace exige que l'appelant accorde le chemin
Le bac à sable n'ouvre que l'espace de travail par défaut (~/Documents/VigieChiro-Companion).
Viser ailleurs demande donc les deux, l'accès et l'option :
flatpak run --filesystem=/chemin/ws fr.echonuit.VigieChiroCompanion \
lister-passages --workspace /chemin/ws
Sans --filesystem, la commande échoue sur un dossier qu'elle ne voit pas, et non sur un refus
explicite.
Depuis les sources¶
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[]) reste le point d'entrée direct, et c'est aussi celui vers lequel Launcher
aiguille dans les emballages : le harnais bats peut donc viser l'un ou l'autre. En CI il vise le
lanceur, par VIGIECHIRO_LANCEUR (#4071) ; sans cette variable, il retombe sur le fat-jar, ce qui
reste la façon la plus rapide de le lancer en local :
./mvnw -q -DskipTests package && bats src/test/bats # sur le fat-jar
VIGIECHIRO_LANCEUR=target/dist/VigieChiroCompanion/bin/vigiechiro \
bats src/test/bats # sur le lanceur livré
Une invocation sans aucun argument rend l'usage de la ligne de commande, elle n'ouvre pas la
fenêtre - y compris pour java -jar vigiechiro-*-shaded.jar, qui demande désormais ihm. Déduire une
demande d'interface de l'absence d'arguments serait la figure que refuse l'ADR 3828 : une condition
ambiante tenant lieu de déclaration.
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.