@@ -241,6 +241,69 @@ def _parse_float_value(value: str | None, default: float) -> float:
241241 return default
242242
243243
244+ def _add_stdin_secret_flags (parser : argparse .ArgumentParser , * , include_ssh : bool ) -> None :
245+ """Attach --password-stdin (and optionally --ssh-password-stdin) to a parser."""
246+ parser .add_argument (
247+ "--password-stdin" ,
248+ dest = "password_stdin" ,
249+ action = "store_true" ,
250+ help = "Read the password from stdin (one line, trailing newline stripped)" ,
251+ )
252+ if include_ssh :
253+ parser .add_argument (
254+ "--ssh-password-stdin" ,
255+ dest = "ssh_password_stdin" ,
256+ action = "store_true" ,
257+ help = "Read the SSH password from stdin (one line, trailing newline stripped)" ,
258+ )
259+
260+
261+ def _resolve_stdin_secrets (args : argparse .Namespace ) -> None :
262+ """Populate args.password / args.url / args.ssh_password from stdin if requested.
263+
264+ Recognised stdin-trigger attrs: ``password_stdin``, ``url_stdin``,
265+ ``ssh_password_stdin``. At most one may be set per invocation — stdin
266+ is a single stream and we read one line from it. The corresponding
267+ cleartext flag must not also be set.
268+ """
269+ from sqlit .domains .connections .domain .stdin_secret import (
270+ StdinSecretError ,
271+ read_secret_from_stdin ,
272+ )
273+
274+ requests : list [tuple [str , str ]] = []
275+ if getattr (args , "password_stdin" , False ):
276+ requests .append (("password" , "password" ))
277+ if getattr (args , "url_stdin" , False ):
278+ requests .append (("url" , "url" ))
279+ if getattr (args , "ssh_password_stdin" , False ):
280+ requests .append (("ssh_password" , "ssh-password" ))
281+
282+ if not requests :
283+ return
284+
285+ if len (requests ) > 1 :
286+ flags = ", " .join (f"--{ label } -stdin" for _ , label in requests )
287+ raise SystemExit (
288+ f"Error: only one of { flags } may be used per invocation "
289+ f"(stdin can only feed one secret)."
290+ )
291+
292+ attr , label = requests [0 ]
293+ existing = getattr (args , attr , None )
294+ if existing :
295+ raise SystemExit (
296+ f"Error: --{ label } and --{ label } -stdin are mutually exclusive."
297+ )
298+
299+ try :
300+ value = read_secret_from_stdin (label = label )
301+ except StdinSecretError as exc :
302+ raise SystemExit (f"Error: { exc } " )
303+
304+ setattr (args , attr , value )
305+
306+
244307def _resolve_startup_log_path (argv : list [str ]) -> Path | None :
245308 env_profile = os .environ .get ("SQLIT_PROFILE_STARTUP" ) == "1"
246309 env_exit = os .environ .get ("SQLIT_PROFILE_STARTUP_EXIT" ) == "1"
@@ -430,7 +493,16 @@ def main() -> int:
430493 parser .add_argument ("--port" , help = "Temporary connection port" )
431494 parser .add_argument ("--database" , help = "Temporary connection database name" )
432495 parser .add_argument ("--username" , help = "Temporary connection username" )
433- parser .add_argument ("--password" , help = "Temporary connection password" )
496+ parser .add_argument (
497+ "--password" ,
498+ help = "Temporary connection password (or use --password-stdin to read from stdin)" ,
499+ )
500+ parser .add_argument (
501+ "--password-stdin" ,
502+ dest = "password_stdin" ,
503+ action = "store_true" ,
504+ help = "Read the password from stdin (one line, trailing newline stripped)" ,
505+ )
434506 parser .add_argument ("--file-path" , help = "Temporary connection file path (SQLite/DuckDB)" )
435507 parser .add_argument (
436508 "--auth-type" ,
@@ -568,13 +640,22 @@ def main() -> int:
568640 add_parser .add_argument (
569641 "--url" ,
570642 metavar = "URL" ,
571- help = "Connection URL (e.g., postgresql://user:pass@host:5432/db). Requires --name." ,
643+ help = (
644+ "Connection URL (e.g., postgresql://user:pass@host:5432/db). "
645+ "Requires --name. Use --url-stdin to read it from stdin instead."
646+ ),
647+ )
648+ add_parser .add_argument (
649+ "--url-stdin" ,
650+ dest = "url_stdin" ,
651+ action = "store_true" ,
652+ help = "Read the connection URL from stdin (one line, trailing newline stripped)" ,
572653 )
573654 add_parser .add_argument (
574655 "--name" ,
575656 "-n" ,
576657 dest = "url_name" ,
577- help = "Connection name (required when using --url)" ,
658+ help = "Connection name (required when using --url / --url-stdin )" ,
578659 )
579660 add_provider_parsers = add_parser .add_subparsers (dest = "provider" , metavar = "PROVIDER" )
580661 for db_type in get_supported_db_types ():
@@ -587,6 +668,7 @@ def main() -> int:
587668 add_schema_arguments (provider_parser , schema , include_name = True , name_required = True )
588669 provider_parser .add_argument ("--password-command" , dest = "password_command" , help = "Shell command to retrieve the database password" )
589670 provider_parser .add_argument ("--ssh-password-command" , dest = "ssh_password_command" , help = "Shell command to retrieve the SSH password" )
671+ _add_stdin_secret_flags (provider_parser , include_ssh = True )
590672 provider_parser .add_argument (
591673 "--alert" ,
592674 metavar = "MODE" ,
@@ -601,7 +683,11 @@ def main() -> int:
601683 edit_parser .add_argument ("--port" , "-P" , help = "Port" )
602684 edit_parser .add_argument ("--database" , "-d" , help = "Database name" )
603685 edit_parser .add_argument ("--username" , "-u" , help = "Username" )
604- edit_parser .add_argument ("--password" , "-p" , help = "Password" )
686+ edit_parser .add_argument (
687+ "--password" ,
688+ "-p" ,
689+ help = "Password (or use --password-stdin to read from stdin)" ,
690+ )
605691 edit_parser .add_argument (
606692 "--auth-type" ,
607693 "-a" ,
@@ -611,6 +697,7 @@ def main() -> int:
611697 edit_parser .add_argument ("--file-path" , help = "Database file path (SQLite only)" )
612698 edit_parser .add_argument ("--password-command" , dest = "password_command" , help = "Shell command to retrieve the database password" )
613699 edit_parser .add_argument ("--ssh-password-command" , dest = "ssh_password_command" , help = "Shell command to retrieve the SSH password" )
700+ _add_stdin_secret_flags (edit_parser , include_ssh = True )
614701 edit_parser .add_argument (
615702 "--alert" ,
616703 metavar = "MODE" ,
@@ -632,6 +719,7 @@ def main() -> int:
632719 add_schema_arguments (provider_parser , schema , include_name = True , name_required = False )
633720 provider_parser .add_argument ("--password-command" , dest = "password_command" , help = "Shell command to retrieve the database password" )
634721 provider_parser .add_argument ("--ssh-password-command" , dest = "ssh_password_command" , help = "Shell command to retrieve the SSH password" )
722+ _add_stdin_secret_flags (provider_parser , include_ssh = True )
635723 provider_parser .add_argument (
636724 "--alert" ,
637725 metavar = "MODE" ,
@@ -705,6 +793,7 @@ def main() -> int:
705793
706794 with startup_span ("cli_parse_args" ):
707795 args = parser .parse_args (filtered_argv [1 :]) # Skip program name
796+ _resolve_stdin_secrets (args )
708797 log_startup_step ("cli_parse_end" )
709798
710799 with startup_span ("runtime_build" ):
0 commit comments