@@ -340,12 +340,21 @@ enum Cmd {
340340 #[ arg( long) ]
341341 json : bool ,
342342 } ,
343- /// Generate a password locally (no agent or server interaction).
343+ /// Generate a password or passphrase locally (no agent or server).
344+ ///
345+ /// With no flags, the `[generate]` config defaults apply (built-in defaults
346+ /// when unset); flags override per-invocation.
344347 Generate {
345- /// Password length in characters.
346- #[ arg( long, short = 'l' , default_value_t = 20 ) ]
347- length : usize ,
348- /// Include symbols (`!@#$%^&*`). Off by default.
348+ /// Generate a diceware passphrase instead of a character password.
349+ #[ arg( long) ]
350+ passphrase : bool ,
351+ /// Force character-password mode (overrides a configured passphrase default).
352+ #[ arg( long) ]
353+ password : bool ,
354+ /// Password length in characters (default 20).
355+ #[ arg( long, short = 'l' ) ]
356+ length : Option < usize > ,
357+ /// Include symbols (`!@#$%^&*`).
349358 #[ arg( long, short = 's' ) ]
350359 symbols : bool ,
351360 /// Exclude lowercase letters.
@@ -357,7 +366,25 @@ enum Cmd {
357366 /// Exclude digits.
358367 #[ arg( long) ]
359368 no_digits : bool ,
360- /// Emit JSON instead of the raw password.
369+ /// Passphrase: number of words, 3–20 (default 3).
370+ #[ arg( long, short = 'w' ) ]
371+ words : Option < usize > ,
372+ /// Passphrase: word separator (default "-").
373+ #[ arg( long) ]
374+ separator : Option < String > ,
375+ /// Passphrase: capitalize the first letter of each word.
376+ #[ arg( long) ]
377+ capitalize : bool ,
378+ /// Passphrase: do not capitalize (overrides a configured default).
379+ #[ arg( long) ]
380+ no_capitalize : bool ,
381+ /// Passphrase: append a digit to one word.
382+ #[ arg( long) ]
383+ include_number : bool ,
384+ /// Passphrase: omit the digit (overrides a configured default).
385+ #[ arg( long) ]
386+ no_include_number : bool ,
387+ /// Emit JSON instead of the raw value.
361388 #[ arg( long) ]
362389 json : bool ,
363390 } ,
@@ -870,31 +897,95 @@ async fn run(cmd: Cmd, ep: Endpoint<'_>) -> Result<(), u8> {
870897 Cmd :: Exec { profile, command } => cmd_exec ( ep, profile, command) . await ,
871898 Cmd :: Purge { force, json } => cmd_purge ( ep, force, json) . await ,
872899 Cmd :: Generate {
900+ passphrase,
901+ password,
873902 length,
874903 symbols,
875904 no_lowercase,
876905 no_uppercase,
877906 no_digits,
907+ words,
908+ separator,
909+ capitalize,
910+ no_capitalize,
911+ include_number,
912+ no_include_number,
878913 json,
879- } => cmd_generate ( length, symbols, no_lowercase, no_uppercase, no_digits, json) ,
914+ } => cmd_generate ( & GenFlags {
915+ passphrase,
916+ password,
917+ length,
918+ symbols,
919+ no_lowercase,
920+ no_uppercase,
921+ no_digits,
922+ words,
923+ separator,
924+ capitalize,
925+ no_capitalize,
926+ include_number,
927+ no_include_number,
928+ json,
929+ } ) ,
880930 }
881931}
882932
883- #[ allow( clippy:: fn_params_excessive_bools) ] // each flag mirrors a `vault generate` CLI switch
884- fn cmd_generate (
885- length : usize ,
933+ /// Parsed `vault generate` switches.
934+ ///
935+ /// Resolved against `[generate]` config in [`cmd_generate`]; each field mirrors
936+ /// one CLI flag.
937+ #[ allow( clippy:: struct_excessive_bools) ] // one bool per CLI switch
938+ struct GenFlags {
939+ passphrase : bool ,
940+ password : bool ,
941+ length : Option < usize > ,
886942 symbols : bool ,
887943 no_lowercase : bool ,
888944 no_uppercase : bool ,
889945 no_digits : bool ,
946+ words : Option < usize > ,
947+ separator : Option < String > ,
948+ capitalize : bool ,
949+ no_capitalize : bool ,
950+ include_number : bool ,
951+ no_include_number : bool ,
890952 json : bool ,
891- ) -> Result < ( ) , u8 > {
953+ }
954+
955+ fn cmd_generate ( f : & GenFlags ) -> Result < ( ) , u8 > {
956+ // Config supplies defaults; explicit flags override. A missing/unreadable
957+ // config falls back to built-in defaults rather than failing generation.
958+ let cfg = config:: load ( ) . unwrap_or_default ( ) ;
959+ let g = cfg. generate ( ) ;
960+
961+ // Mode: explicit --password wins, then --passphrase, then the configured
962+ // default, else character-password.
963+ let passphrase_mode = if f. password {
964+ false
965+ } else if f. passphrase {
966+ true
967+ } else {
968+ g. mode . as_deref ( ) == Some ( "passphrase" )
969+ } ;
970+
971+ if passphrase_mode {
972+ cmd_generate_passphrase ( f, g)
973+ } else {
974+ cmd_generate_password ( f, g)
975+ }
976+ }
977+
978+ fn cmd_generate_password ( f : & GenFlags , g : & config:: GenerateCfg ) -> Result < ( ) , u8 > {
892979 let opts = vault_core:: GenerateOptions {
893- length,
894- lowercase : !no_lowercase,
895- uppercase : !no_uppercase,
896- digits : !no_digits,
897- symbols,
980+ length : f
981+ . length
982+ . or_else ( || g. length . and_then ( |l| usize:: try_from ( l) . ok ( ) ) )
983+ . unwrap_or ( 20 ) ,
984+ // `--no-*` forces a class off; config (else built-in default) is the baseline.
985+ lowercase : g. lowercase . unwrap_or ( true ) && !f. no_lowercase ,
986+ uppercase : g. uppercase . unwrap_or ( true ) && !f. no_uppercase ,
987+ digits : g. digits . unwrap_or ( true ) && !f. no_digits ,
988+ symbols : f. symbols || g. symbols . unwrap_or ( false ) ,
898989 } ;
899990 let pw = match vault_core:: generate_password ( & opts) {
900991 Ok ( p) => p,
@@ -903,7 +994,7 @@ fn cmd_generate(
903994 return Err ( 2 ) ;
904995 }
905996 } ;
906- if json {
997+ if f . json {
907998 let v = serde_json:: json!( {
908999 "password" : pw. as_str( ) ,
9091000 "length" : opts. length,
@@ -921,6 +1012,44 @@ fn cmd_generate(
9211012 Ok ( ( ) )
9221013}
9231014
1015+ fn cmd_generate_passphrase ( f : & GenFlags , g : & config:: GenerateCfg ) -> Result < ( ) , u8 > {
1016+ let opts = vault_core:: PassphraseOptions {
1017+ words : f
1018+ . words
1019+ . or_else ( || g. words . and_then ( |w| usize:: try_from ( w) . ok ( ) ) )
1020+ . unwrap_or ( 3 ) ,
1021+ separator : f
1022+ . separator
1023+ . clone ( )
1024+ . or_else ( || g. separator . clone ( ) )
1025+ . unwrap_or_else ( || "-" . to_owned ( ) ) ,
1026+ // A positive flag (or config) turns it on; a `--no-*` flag forces it off.
1027+ capitalize : ( f. capitalize || g. capitalize . unwrap_or ( false ) ) && !f. no_capitalize ,
1028+ include_number : ( f. include_number || g. include_number . unwrap_or ( false ) )
1029+ && !f. no_include_number ,
1030+ } ;
1031+ let phrase = match vault_core:: generate_passphrase ( & opts) {
1032+ Ok ( p) => p,
1033+ Err ( e) => {
1034+ eprintln ! ( "vault: {e}" ) ;
1035+ return Err ( 2 ) ;
1036+ }
1037+ } ;
1038+ if f. json {
1039+ let v = serde_json:: json!( {
1040+ "passphrase" : phrase. as_str( ) ,
1041+ "words" : opts. words,
1042+ "separator" : opts. separator,
1043+ "capitalize" : opts. capitalize,
1044+ "include_number" : opts. include_number,
1045+ } ) ;
1046+ println ! ( "{v}" ) ;
1047+ } else {
1048+ println ! ( "{}" , phrase. as_str( ) ) ;
1049+ }
1050+ Ok ( ( ) )
1051+ }
1052+
9241053async fn cmd_status ( ep : Endpoint < ' _ > , json : bool ) -> Result < ( ) , u8 > {
9251054 let mut stream = connect ( ep) . await ?;
9261055 let resp = exchange ( & mut stream, & Request :: Status ) . await ?;
0 commit comments