@@ -9,7 +9,7 @@ use secrecy::{ExposeSecret, SecretString};
99use std:: collections:: { HashMap , HashSet } ;
1010use std:: convert:: TryFrom ;
1111use std:: env;
12- use std:: io:: { self , IsTerminal , Read , Write } ;
12+ use std:: io:: { self , IsTerminal , Read } ;
1313use std:: path:: Path ;
1414use std:: process:: Command ;
1515
@@ -552,11 +552,11 @@ impl Secrets {
552552 let value = if let Some ( v) = value {
553553 SecretString :: new ( v. into ( ) )
554554 } else if io:: stdin ( ) . is_terminal ( ) {
555- // Use rpassword for single-line input (most common case)
556- // For multiline secrets, users should pipe the content
557- let secret = rpassword :: prompt_password ( format ! (
558- "Enter value for {name} (profile: {profile_name}): "
559- ) ) ?;
555+ let secret = inquire :: Password :: new ( & format ! (
556+ "Enter value for {name} (profile: {profile_name}):"
557+ ) )
558+ . without_confirmation ( )
559+ . prompt ( ) ?;
560560 SecretString :: new ( secret. into ( ) )
561561 } else {
562562 // Read from stdin when input is piped
@@ -696,29 +696,61 @@ impl Secrets {
696696 Err ( validation_errors) => {
697697 // If we're in interactive mode and have missing required secrets, prompt for them
698698 if interactive && !validation_errors. missing_required . is_empty ( ) {
699- eprintln ! ( "\n The following required secrets are missing:" ) ;
700- for secret_name in & validation_errors. missing_required {
699+ if !io:: stdin ( ) . is_terminal ( ) {
700+ return Err ( SecretSpecError :: RequiredSecretMissing (
701+ validation_errors. missing_required . join ( ", " ) ,
702+ ) ) ;
703+ }
704+
705+ let missing = & validation_errors. missing_required ;
706+ let total = missing. len ( ) ;
707+ let default_backend = self . get_provider ( provider_arg. clone ( ) ) ?;
708+
709+ // List all missing secrets upfront
710+ eprintln ! (
711+ "\n {} required {} missing in profile {} with provider {}:\n " ,
712+ total,
713+ if total == 1 {
714+ "secret is"
715+ } else {
716+ "secrets are"
717+ } ,
718+ profile_display. bold( ) ,
719+ default_backend. name( ) . bold( ) ,
720+ ) ;
721+ for secret_name in missing {
722+ let description = self
723+ . resolve_secret_config ( secret_name, Some ( & profile_display) )
724+ . and_then ( |c| c. description )
725+ . unwrap_or_default ( ) ;
726+ if description. is_empty ( ) {
727+ eprintln ! ( " {} {}" , "-" . dimmed( ) , secret_name. bold( ) ) ;
728+ } else {
729+ eprintln ! (
730+ " {} {} - {}" ,
731+ "-" . dimmed( ) ,
732+ secret_name. bold( ) ,
733+ description
734+ ) ;
735+ }
736+ }
737+ eprintln ! ( ) ;
738+
739+ // Prompt for each missing secret
740+ for ( i, secret_name) in missing. iter ( ) . enumerate ( ) {
701741 if let Some ( secret_config) =
702742 self . resolve_secret_config ( secret_name, Some ( & profile_display) )
703743 {
704- let description = secret_config
705- . description
706- . as_deref ( )
707- . unwrap_or ( "No description" ) ;
708- eprintln ! ( "\n {} - {}" , secret_name. bold( ) , description) ;
709- let value = if io:: stdin ( ) . is_terminal ( ) {
710- print ! (
711- "Enter value for {} (profile: {}): " ,
712- secret_name, profile_display
713- ) ;
714- io:: stdout ( ) . flush ( ) ?;
715- rpassword:: read_password ( ) ?
716- } else {
717- // When stdin is not a terminal, we can't prompt interactively
718- return Err ( SecretSpecError :: RequiredSecretMissing (
719- validation_errors. missing_required . join ( ", " ) ,
720- ) ) ;
721- } ;
744+ let prompt_msg =
745+ format ! ( "[{}/{}] Enter value for {}:" , i + 1 , total, secret_name, ) ;
746+ let mut prompt =
747+ inquire:: Password :: new ( & prompt_msg) . without_confirmation ( ) ;
748+
749+ if let Some ( ref desc) = secret_config. description {
750+ prompt = prompt. with_help_message ( desc) ;
751+ }
752+
753+ let value = prompt. prompt ( ) ?;
722754
723755 // Get the provider for this specific secret
724756 // Use first provider in list if specified, otherwise use CLI provider or default
0 commit comments