mnemo/cli.rs
1use clap::{Parser, Subcommand, ValueEnum};
2use clap_complete::Shell;
3use std::path::PathBuf;
4
5use crate::export::ExportFormat;
6
7#[derive(Parser, Debug)]
8#[command(
9 name = "mnemo",
10 version,
11 about = "Navigation et recherche dans l'historique Bash",
12 long_about = None,
13)]
14pub struct Cli {
15 #[command(subcommand)]
16 pub command: Command,
17}
18
19#[derive(Subcommand, Debug)]
20pub enum Command {
21 /// Initialise la configuration et la base de données.
22 Init {
23 /// Lance l'assistant d'onboarding interactif (intégration Bash, import,
24 /// diagnostic). Toutes les actions proposées sont non destructives.
25 #[arg(long)]
26 wizard: bool,
27 /// En mode `--wizard` non interactif, accepte les choix sûrs par défaut
28 /// sans rien supprimer ni purger.
29 #[arg(long)]
30 yes: bool,
31 },
32
33 /// Génère un script de complétion shell sur stdout (bash, zsh, fish).
34 ///
35 /// mnemo n'écrit jamais dans vos fichiers shell : redirigez la sortie vers
36 /// l'emplacement adéquat (voir `docs/UX_ONBOARDING.md`).
37 Completions {
38 /// Shell cible.
39 #[arg(value_enum)]
40 shell: CompletionShell,
41 },
42
43 /// Importe l'historique Bash (~/.bash_history par défaut) dans la base.
44 Import {
45 /// Fichier d'historique à importer.
46 #[arg(long)]
47 file: Option<PathBuf>,
48 },
49
50 /// Ajoute une commande dans la base.
51 Add {
52 /// Commande à enregistrer.
53 #[arg(long)]
54 cmd: String,
55 /// Répertoire de travail (défaut : répertoire courant).
56 #[arg(long)]
57 cwd: Option<String>,
58 /// Code de sortie de la commande.
59 #[arg(long = "exit-code", default_value_t = 0)]
60 exit_code: i64,
61 },
62
63 /// Ouvre l'interface TUI interactive de recherche.
64 Search {
65 /// Requête initiale (positionnelle, optionnelle).
66 query: Option<String>,
67 /// Requête explicite (équivalent à l'argument positionnel).
68 #[arg(long = "query", value_name = "TEXTE", conflicts_with = "query")]
69 query_opt: Option<String>,
70 /// Mode non interactif : imprime les résultats sur stdout sans TUI.
71 #[arg(long)]
72 print: bool,
73 /// Nombre maximal de résultats affichés en mode --print.
74 #[arg(long, default_value_t = 20)]
75 limit: usize,
76 /// Filtre sur un projet Git (nom du dossier racine ou chemin git_root).
77 #[arg(long, value_name = "NOM")]
78 project: Option<String>,
79 /// Filtre sur une branche Git.
80 #[arg(long, value_name = "BRANCHE")]
81 branch: Option<String>,
82 /// Filtre sur un code de sortie exact (ex : 0, 1, 127).
83 #[arg(long = "exit-code", value_name = "CODE")]
84 exit_code: Option<i64>,
85 /// N'affiche que les commandes en échec (exit_code ≠ 0).
86 #[arg(long, conflicts_with = "exit_code")]
87 failed: bool,
88 /// Limite l'âge des résultats (durée `24h`/`7d`/`2w`/`3m`/`1y` ou date `AAAA-MM-JJ`).
89 #[arg(long, value_name = "DURÉE|DATE")]
90 since: Option<String>,
91 /// N'affiche que les commandes antérieures à une date (`AAAA-MM-JJ`).
92 /// Alias : `--until`.
93 #[arg(long, visible_alias = "until", value_name = "DATE")]
94 before: Option<String>,
95 /// Filtre sur un répertoire de travail exact.
96 #[arg(long, value_name = "CHEMIN")]
97 cwd: Option<String>,
98 /// Filtre sur un shell exact (ex : bash, zsh).
99 #[arg(long, value_name = "SHELL")]
100 shell: Option<String>,
101 /// Produit une sortie JSON stable (implique le mode non interactif).
102 #[arg(long)]
103 json: bool,
104 /// N'affiche que les identifiants, un par ligne (implique le mode non
105 /// interactif). Pratique pour chaîner avec `mnemo show`/`mnemo print`.
106 #[arg(long = "id-only", conflicts_with = "json")]
107 id_only: bool,
108 },
109
110 /// Ouvre la TUI avancée (interface interactive principale).
111 Tui {
112 /// Requête initiale (positionnelle, optionnelle).
113 query: Option<String>,
114 /// Filtre initial sur un projet Git (nom du dossier racine).
115 #[arg(long, value_name = "NOM")]
116 project: Option<String>,
117 /// Filtre initial sur une branche Git.
118 #[arg(long, value_name = "BRANCHE")]
119 branch: Option<String>,
120 /// Filtre initial sur un répertoire de travail.
121 #[arg(long, value_name = "CHEMIN")]
122 cwd: Option<String>,
123 /// N'affiche que les commandes en échec (exit_code ≠ 0).
124 #[arg(long)]
125 failed: bool,
126 },
127
128 /// Affiche le snippet d'intégration Bash à ajouter dans ~/.bashrc.
129 Bashrc,
130
131 /// Gère l'intégration shell installée dans ~/.bashrc.
132 Shell {
133 #[command(subcommand)]
134 action: ShellCommand,
135 },
136
137 /// Applique les migrations de schéma SQLite en attente.
138 Migrate,
139
140 /// Affiche des statistiques d'usage (texte simple).
141 Stats {
142 /// Filtre sur un projet Git (nom du dossier racine, chemin git_root, ou `current`).
143 #[arg(long, value_name = "NOM")]
144 project: Option<String>,
145 /// Filtre sur une branche Git.
146 #[arg(long, value_name = "BRANCHE")]
147 branch: Option<String>,
148 /// Limite la fenêtre d'analyse (durée `24h`/`7d`/`2w`/`3m`/`1y` ou date `AAAA-MM-JJ`).
149 #[arg(long, value_name = "DURÉE|DATE")]
150 since: Option<String>,
151 /// Produit une sortie JSON exploitable.
152 #[arg(long)]
153 json: bool,
154 },
155
156 /// Diagnostique l'installation locale de mnemo.
157 Doctor {
158 /// Répare les éléments manquants (config, base, bloc .bashrc).
159 #[arg(long)]
160 fix: bool,
161 /// Produit une sortie JSON exploitable.
162 #[arg(long)]
163 json: bool,
164 },
165
166 /// Gère la configuration locale de mnemo.
167 Config {
168 #[command(subcommand)]
169 action: ConfigCommand,
170 },
171
172 /// Crée une sauvegarde locale complète (archive .tar.gz).
173 Backup {
174 /// Dossier de destination (défaut : ~/.local/share/mnemo/backups/).
175 #[arg(long, value_name = "DOSSIER")]
176 output: Option<PathBuf>,
177 /// Produit une sortie JSON exploitable.
178 #[arg(long)]
179 json: bool,
180 },
181
182 /// Restaure une sauvegarde (.tar.gz) après vérification.
183 Restore {
184 /// Chemin de l'archive de sauvegarde.
185 archive: PathBuf,
186 /// Montre ce qui serait fait sans rien modifier.
187 #[arg(long = "dry-run")]
188 dry_run: bool,
189 /// Confirme la restauration sans question interactive.
190 #[arg(long)]
191 yes: bool,
192 },
193
194 /// Exporte les commandes en JSON ou CSV.
195 Export {
196 /// Format de sortie.
197 #[arg(long, value_enum)]
198 format: ExportFormat,
199 /// Filtre sur un projet Git (nom du dossier racine ou chemin git_root).
200 #[arg(long, value_name = "NOM")]
201 project: Option<String>,
202 /// Filtre sur une branche Git.
203 #[arg(long, value_name = "BRANCHE")]
204 branch: Option<String>,
205 /// Fichier de sortie (défaut : stdout).
206 #[arg(long, value_name = "FICHIER")]
207 output: Option<PathBuf>,
208 /// Compresse la sortie en gzip (`.json.gz` / `.csv.gz`).
209 #[arg(long)]
210 gzip: bool,
211 },
212
213 /// Affiche les dernières commandes avec leurs IDs.
214 List {
215 /// Nombre de commandes affichées (défaut : 20).
216 #[arg(long)]
217 limit: Option<usize>,
218 /// Filtre sur un projet Git (nom du dossier racine ou chemin git_root).
219 #[arg(long, value_name = "NOM")]
220 project: Option<String>,
221 /// Filtre sur une branche Git.
222 #[arg(long, value_name = "BRANCHE")]
223 branch: Option<String>,
224 /// Produit une sortie JSON exploitable.
225 #[arg(long)]
226 json: bool,
227 },
228
229 /// Affiche le détail complet d'une commande par son ID, sans l'exécuter.
230 ///
231 /// mnemo n'exécute jamais une commande de l'historique : `show` se contente
232 /// de lire la base. Si la commande a déjà été redactée, sa forme redactée
233 /// stockée est affichée telle quelle.
234 Show {
235 /// Identifiant de la commande (voir `mnemo list` ou `mnemo search`).
236 id: i64,
237 },
238
239 /// Imprime uniquement la commande brute sur stdout, sans décor ni exécution.
240 ///
241 /// Aucun label, aucune couleur : la sortie peut être copiée ou redirigée.
242 /// mnemo n'exécute jamais la commande ; l'utilisateur reste responsable de
243 /// ce qu'il fait de la sortie.
244 Print {
245 /// Identifiant de la commande (voir `mnemo list` ou `mnemo search`).
246 id: i64,
247 },
248
249 /// Supprime une commande par son ID (après confirmation).
250 Delete {
251 /// Identifiant de la commande à supprimer.
252 id: i64,
253 /// Montre la commande ciblée sans la supprimer.
254 #[arg(long = "dry-run")]
255 dry_run: bool,
256 /// Confirme la suppression sans question interactive.
257 #[arg(long)]
258 yes: bool,
259 },
260
261 /// Nettoie les commandes plus anciennes qu'une durée donnée.
262 Prune {
263 /// Durée d'ancienneté (ex : 30d, 12w, 6m, 1y).
264 #[arg(long = "older-than", value_name = "DURÉE")]
265 older_than: String,
266 /// Filtre sur un projet Git (nom du dossier racine ou chemin git_root).
267 #[arg(long, value_name = "NOM")]
268 project: Option<String>,
269 /// Filtre sur une branche Git.
270 #[arg(long, value_name = "BRANCHE")]
271 branch: Option<String>,
272 /// Montre ce qui serait supprimé sans rien modifier.
273 #[arg(long = "dry-run")]
274 dry_run: bool,
275 /// Confirme le nettoyage sans question interactive.
276 #[arg(long)]
277 yes: bool,
278 },
279
280 /// Affiche des informations détaillées de version et de build.
281 Version,
282
283 /// Vérifie si une nouvelle version est disponible (sans rien installer).
284 ///
285 /// En terminal interactif, si une mise à jour existe, propose de lancer
286 /// `mnemo upgrade` immédiatement (réponse par défaut : non). En mode non
287 /// interactif (CI, script, cron, pipe), reste une simple vérification.
288 /// `--upgrade` enchaîne directement l'installation quand une mise à jour est
289 /// disponible ; combiné à `--yes`, il permet un upgrade automatisé.
290 /// `--require-signature` rend la vérification Sigstore (cosign) obligatoire
291 /// lors de l'upgrade enchaîné.
292 Update {
293 /// Sortie au format JSON (vérification seule, sans proposition).
294 #[arg(long)]
295 json: bool,
296 /// Si une mise à jour est disponible, lance directement `mnemo upgrade`.
297 #[arg(long)]
298 upgrade: bool,
299 /// Avec `--upgrade`, installe sans confirmation interactive.
300 #[arg(long)]
301 yes: bool,
302 /// Avec `--upgrade`, exige une signature Sigstore valide (cosign).
303 #[arg(long = "require-signature")]
304 require_signature: bool,
305 },
306
307 /// Télécharge et installe la dernière version stable (remplace le binaire).
308 Upgrade {
309 /// Montre ce qui serait fait sans rien télécharger ni remplacer.
310 #[arg(long = "dry-run")]
311 dry_run: bool,
312 /// Confirme l'installation sans question interactive.
313 #[arg(long)]
314 yes: bool,
315 /// Force une version précise (ex : v0.5.0) au lieu de la dernière.
316 #[arg(long, value_name = "VERSION")]
317 version: Option<String>,
318 /// Force un triplet cible (ex : aarch64-unknown-linux-musl).
319 #[arg(long, value_name = "CIBLE")]
320 target: Option<String>,
321 /// Exige une signature Sigstore valide (cosign requis) avant d'installer.
322 #[arg(long = "require-signature")]
323 require_signature: bool,
324 },
325
326 /// Désinstalle mnemo : binaire + intégration shell. Conserve les données.
327 Uninstall {
328 /// Montre ce qui serait supprimé sans rien modifier.
329 #[arg(long = "dry-run")]
330 dry_run: bool,
331 /// Confirme la désinstallation sans question interactive.
332 #[arg(long)]
333 yes: bool,
334 /// Supprime AUSSI la configuration, la base et les sauvegardes.
335 #[arg(long)]
336 purge: bool,
337 },
338
339 /// Inspecte le projet courant et les projets connus de l'historique.
340 Project {
341 #[command(subcommand)]
342 action: ProjectCommand,
343 },
344
345 /// Maintenance de l'historique (nettoyage automatique configurable).
346 Maintenance {
347 #[command(subcommand)]
348 action: MaintenanceCommand,
349 },
350
351 /// Navigue, consulte et exporte des sessions de travail.
352 ///
353 /// Une session regroupe les commandes partageant un même `session_id`,
354 /// capturé par l'intégration shell (`MNEMO_SESSION_ID`). Les commandes
355 /// importées ou enregistrées sans cet identifiant ne sont pas rattachées à
356 /// une session.
357 Session {
358 #[command(subcommand)]
359 action: SessionCommand,
360 },
361
362 /// Analyse et redacte les secrets présents dans l'historique déjà stocké.
363 ///
364 /// `scan` repère les commandes potentiellement sensibles et les affiche
365 /// toujours sous forme redactée. `redact` les nettoie en place (dry-run par
366 /// défaut, sauvegarde obligatoire avant toute écriture). Aucun secret n'est
367 /// jamais affiché en clair.
368 Secrets {
369 #[command(subcommand)]
370 action: SecretsCommand,
371 },
372}
373
374/// Regroupe les options de `mnemo search` pour éviter une fonction à trop
375/// d'arguments (filtres combinables passés en un bloc).
376#[derive(Debug, Default)]
377pub struct SearchArgs {
378 pub query: Option<String>,
379 pub print: bool,
380 pub limit: usize,
381 pub project: Option<String>,
382 pub branch: Option<String>,
383 pub exit_code: Option<i64>,
384 pub failed: bool,
385 pub since: Option<String>,
386 pub before: Option<String>,
387 pub cwd: Option<String>,
388 pub shell: Option<String>,
389 pub json: bool,
390 pub id_only: bool,
391}
392
393/// Shells supportés par `mnemo completions`. Limité volontairement à bash, zsh
394/// et fish (un shell inconnu produit une erreur claire de clap). L'enregistrement
395/// automatique du hook reste, lui, Bash-first.
396#[derive(Clone, Copy, Debug, ValueEnum)]
397pub enum CompletionShell {
398 Bash,
399 Zsh,
400 Fish,
401}
402
403impl CompletionShell {
404 /// Convertit vers le générateur `clap_complete` correspondant.
405 pub fn generator(self) -> Shell {
406 match self {
407 CompletionShell::Bash => Shell::Bash,
408 CompletionShell::Zsh => Shell::Zsh,
409 CompletionShell::Fish => Shell::Fish,
410 }
411 }
412}
413
414#[derive(Subcommand, Debug)]
415pub enum ConfigCommand {
416 /// Affiche la configuration effective (valeurs par défaut incluses).
417 Show,
418 /// Affiche le chemin du fichier de configuration.
419 Path,
420 /// Ouvre la configuration dans l'éditeur ($EDITOR, sinon nano/vi).
421 Edit,
422 /// Vérifie la validité du fichier de configuration.
423 Validate,
424 /// Gère la liste des commandes ignorées dans `mnemo stats`.
425 StatsIgnore {
426 #[command(subcommand)]
427 action: StatsIgnoreCommand,
428 },
429}
430
431#[derive(Subcommand, Debug)]
432pub enum ProjectCommand {
433 /// Affiche le projet détecté pour le répertoire courant.
434 Current,
435 /// Liste les projets connus de l'historique.
436 List {
437 /// Nombre maximal de projets affichés.
438 #[arg(long, value_name = "N")]
439 limit: Option<usize>,
440 /// Sortie JSON.
441 #[arg(long)]
442 json: bool,
443 },
444 /// Affiche le détail d'un projet : activité, branches, derniers échecs.
445 Show {
446 /// Racine ou nom court du projet (incompatible avec `--current`).
447 #[arg(value_name = "PROJET", conflicts_with = "current")]
448 project: Option<String>,
449 /// Cible le projet du répertoire courant.
450 #[arg(long)]
451 current: bool,
452 /// Nombre maximal de commandes récentes affichées.
453 #[arg(long, value_name = "N")]
454 limit: Option<usize>,
455 /// Sortie JSON.
456 #[arg(long)]
457 json: bool,
458 },
459 /// Génère un rapport d'activité réutilisable (Markdown par défaut, ou JSON).
460 Report {
461 /// Racine ou nom court du projet (incompatible avec `--current`).
462 #[arg(value_name = "PROJET", conflicts_with = "current")]
463 project: Option<String>,
464 /// Cible le projet du répertoire courant.
465 #[arg(long)]
466 current: bool,
467 /// Borne inférieure (`24h`, `7d`, `2w`, `3m`, `1y` ou `AAAA-MM-JJ`).
468 #[arg(long, value_name = "DURÉE|DATE")]
469 since: Option<String>,
470 /// Borne supérieure (`AAAA-MM-JJ` exclue, ou durée).
471 #[arg(long, value_name = "DURÉE|DATE")]
472 until: Option<String>,
473 /// Format de sortie (`markdown` par défaut).
474 #[arg(long, value_enum, default_value = "markdown")]
475 format: SessionFormat,
476 /// Fichier de sortie (défaut : stdout).
477 #[arg(long, value_name = "FICHIER")]
478 output: Option<PathBuf>,
479 /// Autorise l'écrasement d'un fichier de sortie existant.
480 #[arg(long)]
481 force: bool,
482 /// Nombre maximal de commandes détaillées dans le rapport.
483 #[arg(long, value_name = "N")]
484 limit: Option<usize>,
485 },
486}
487
488#[derive(Subcommand, Debug)]
489pub enum MaintenanceCommand {
490 /// Affiche l'état de la maintenance et ce qui serait nettoyé.
491 Status,
492 /// Exécute le nettoyage configuré.
493 Run {
494 /// Montre ce qui serait supprimé sans rien modifier.
495 #[arg(long = "dry-run")]
496 dry_run: bool,
497 /// Confirme le nettoyage sans question interactive.
498 #[arg(long)]
499 yes: bool,
500 },
501}
502
503#[derive(Subcommand, Debug)]
504pub enum StatsIgnoreCommand {
505 /// Ajoute une commande à la liste ignorée du Top commandes.
506 Add {
507 /// Nom de commande (ex: `create_dir`).
508 name: String,
509 },
510 /// Retire une commande de la liste ignorée.
511 Remove {
512 /// Nom de commande (ex: `create_dir`).
513 name: String,
514 },
515 /// Affiche les commandes actuellement ignorées.
516 List,
517}
518
519#[derive(Subcommand, Debug)]
520pub enum ShellCommand {
521 /// Met à niveau l'intégration Bash installée dans ~/.bashrc.
522 ///
523 /// Remplace un bloc obsolète par la version courante (capture de
524 /// `MNEMO_SESSION_ID` pour `mnemo session`), après sauvegarde et sans
525 /// toucher au reste du fichier. Sans bloc installé, propose `mnemo init`.
526 Upgrade,
527}
528
529/// Format d'export d'une session (`mnemo session export`).
530#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
531pub enum SessionFormat {
532 Markdown,
533 Json,
534}
535
536#[derive(Subcommand, Debug)]
537pub enum SessionCommand {
538 /// Liste les sessions connues, de la plus récente à la plus ancienne.
539 List {
540 /// Nombre maximal de sessions affichées.
541 #[arg(long, value_name = "N")]
542 limit: Option<usize>,
543 },
544 /// Affiche les commandes d'une session, dans l'ordre chronologique.
545 Show {
546 /// Identifiant de session (voir `mnemo session list`).
547 session_id: String,
548 /// Nombre maximal de commandes affichées.
549 #[arg(long, value_name = "N")]
550 limit: Option<usize>,
551 },
552 /// Exporte une session en Markdown (défaut) ou JSON.
553 Export {
554 /// Identifiant de session à exporter (incompatible avec `--last`).
555 #[arg(value_name = "SESSION_ID", conflicts_with = "last")]
556 session_id: Option<String>,
557 /// Cible la session la plus récente au lieu d'un identifiant explicite.
558 #[arg(long)]
559 last: bool,
560 /// Format de sortie (`markdown` par défaut).
561 #[arg(long, value_enum, default_value = "markdown")]
562 format: SessionFormat,
563 /// Fichier de sortie (défaut : stdout).
564 #[arg(long, value_name = "FICHIER")]
565 output: Option<PathBuf>,
566 /// Autorise l'écrasement d'un fichier de sortie existant.
567 #[arg(long)]
568 force: bool,
569 },
570}
571
572#[derive(Subcommand, Debug)]
573pub enum SecretsCommand {
574 /// Repère les commandes potentiellement sensibles (lecture seule).
575 ///
576 /// Les commandes sont toujours affichées sous forme redactée ; aucun secret
577 /// n'apparaît en clair. N'effectue aucune modification.
578 Scan {
579 /// Nombre maximal de résultats affichés.
580 #[arg(long, value_name = "N")]
581 limit: Option<usize>,
582 /// Sortie JSON (sans valeurs sensibles).
583 #[arg(long)]
584 json: bool,
585 },
586 /// Redacte en place les commandes sensibles déjà stockées.
587 ///
588 /// Dry-run par défaut : sans `--apply`, rien n'est modifié. Avec `--apply`,
589 /// une sauvegarde est créée avant toute écriture et seule la colonne
590 /// `command` est mise à jour.
591 Redact {
592 /// Montre ce qui serait redacté sans rien modifier (comportement par
593 /// défaut, accepté explicitement).
594 #[arg(long = "dry-run")]
595 dry_run: bool,
596 /// Applique réellement la redaction (sinon dry-run).
597 #[arg(long)]
598 apply: bool,
599 /// Confirme la redaction sans question interactive.
600 #[arg(long)]
601 yes: bool,
602 /// Force une sauvegarde avant redaction (toujours effectuée avec
603 /// `--apply`, ce drapeau le rend explicite).
604 #[arg(long)]
605 backup: bool,
606 },
607}