77import re
88
99import click
10+ from prompt_toolkit import PromptSession
11+ from prompt_toolkit .formatted_text import HTML
12+ from prompt_toolkit .key_binding import KeyBindings
13+ from prompt_toolkit .styles import Style
1014from rich .console import Console
1115from rich .progress import Progress , SpinnerColumn , TextColumn
1216from rich .prompt import Confirm
2327repo_manager = RepositoryManager ()
2428profile_config_manager = ProfileConfigManager ()
2529
30+ # Create a prompt session with custom styling
31+ prompt_session = PromptSession ()
32+ style = Style .from_dict (
33+ {
34+ "prompt" : "ansicyan bold" ,
35+ "default" : "ansiyellow" ,
36+ "description" : "ansiwhite" ,
37+ "required" : "ansired" ,
38+ "optional" : "ansigreen" ,
39+ }
40+ )
41+
42+ # Create key bindings
43+ kb = KeyBindings ()
44+
45+
46+ def prompt_with_default (prompt_text , default = "" , hide_input = False , required = False ):
47+ """Prompt the user with a default value that can be edited directly.
48+
49+ Args:
50+ prompt_text: The prompt text to display
51+ default: The default value to show in the prompt
52+ hide_input: Whether to hide the input (for passwords)
53+ required: Whether this is a required field
54+
55+ Returns:
56+ The user's input or the default value if empty
57+ """
58+ # if default:
59+ # console.print(f"Default: [yellow]{default}[/]")
60+
61+ # Get user input
62+ try :
63+ result = prompt_session .prompt (
64+ message = HTML (f"<prompt>{ prompt_text } </prompt> > " ),
65+ style = style ,
66+ default = default ,
67+ is_password = hide_input ,
68+ key_bindings = kb ,
69+ )
70+
71+ # Empty result for non-required field means leave it empty
72+ if not result .strip () and not required :
73+ return ""
74+
75+ # Empty result for required field with default should use default
76+ if not result .strip () and required and default :
77+ return default
78+
79+ # Empty result for required field without default is not allowed
80+ if not result .strip () and required and not default :
81+ console .print ("[yellow]Warning: Required value cannot be empty.[/]" )
82+ return prompt_with_default (prompt_text , default , hide_input , required )
83+
84+ return result
85+ except KeyboardInterrupt :
86+ raise click .Abort ()
87+
2688
2789@click .command ()
2890@click .argument ("server_name" )
@@ -222,33 +284,36 @@ def add(server_name, force=False, alias=None, target: str | None = None):
222284 description = arg_info .get ("description" , "" )
223285 is_required = arg_info .get ("required" , False )
224286 example = arg_info .get ("example" , "" )
225- example_text = f" (example: { example } )" if example else ""
226-
227- # Build prompt text
228- prompt_text = f"Enter value for { arg_name } { example_text } "
229- if description :
230- prompt_text += f"\n { description } "
231287
232288 # Add required indicator
233289 if is_required :
234290 required_args [arg_name ] = arg_info
235- prompt_text += " (required)"
291+ required_text = "(required)"
292+ required_html = "<ansired>(required)</ansired>"
236293 else :
237- prompt_text += " (optional, press Enter to skip)"
294+ required_text = "(optional)"
295+ required_html = "<ansigreen>(optional)</ansigreen>"
296+
297+ # Build clean prompt text for console display and prompt
298+ console_prompt_text = f"{ arg_name } { required_text } "
299+ html_prompt_text = f"{ arg_name } { required_html } "
238300
239301 # Check if the argument is already set in environment
240302 env_value = os .environ .get (arg_name , "" )
241303
304+ # Print description if available
305+ if description :
306+ console .print (f"[dim]{ description } [/]" )
307+
242308 if env_value :
243309 # Show the existing value as default
244- console .print (f"[green]Found { arg_name } in environment: { env_value } [/]" )
310+ console .print (f"[green]Found in environment: { env_value } [/]" )
245311 try :
246- user_value = click . prompt (
247- prompt_text ,
312+ user_value = prompt_with_default (
313+ html_prompt_text ,
248314 default = env_value ,
249- hide_input = "token" in arg_name .lower ()
250- or "key" in arg_name .lower ()
251- or "secret" in arg_name .lower (),
315+ hide_input = _should_hide_input (arg_name ),
316+ required = is_required ,
252317 )
253318 if user_value != env_value :
254319 # User provided a different value
@@ -261,29 +326,17 @@ def add(server_name, force=False, alias=None, target: str | None = None):
261326 else :
262327 # No environment value
263328 try :
264- if is_required :
265- # Required argument must have a value
266- user_value = click .prompt (
267- prompt_text ,
268- hide_input = "token" in arg_name .lower ()
269- or "key" in arg_name .lower ()
270- or "secret" in arg_name .lower (),
271- )
329+ user_value = prompt_with_default (
330+ html_prompt_text ,
331+ default = example if example else "" ,
332+ hide_input = _should_hide_input (arg_name ),
333+ required = is_required ,
334+ )
335+
336+ # Only add non-empty values to the environment
337+ if user_value and user_value .strip ():
272338 processed_variables [arg_name ] = user_value
273- else :
274- # Optional argument can be skipped
275- user_value = click .prompt (
276- prompt_text ,
277- default = "" ,
278- show_default = False ,
279- hide_input = "token" in arg_name .lower ()
280- or "key" in arg_name .lower ()
281- or "secret" in arg_name .lower (),
282- )
283- # Only add non-empty values to the environment
284- if user_value and user_value .strip ():
285- processed_variables [arg_name ] = user_value
286- # Explicitly don't add anything if the user leaves it blank
339+ # Explicitly don't add anything if the user leaves it blank
287340 except click .Abort :
288341 if is_required :
289342 console .print (f"[yellow]Warning: Required argument { arg_name } not provided.[/]" )
@@ -309,9 +362,11 @@ def add(server_name, force=False, alias=None, target: str | None = None):
309362 arg_info = required_args .get (key , {})
310363 description = arg_info .get ("description" , "" )
311364 try :
312- user_value = click . prompt (
365+ user_value = prompt_with_default (
313366 f"Enter value for { key } ({ description } )" ,
314- hide_input = "token" in key .lower () or "key" in key .lower (),
367+ default = "" ,
368+ hide_input = _should_hide_input (key ),
369+ required = True ,
315370 )
316371 processed_env [key ] = user_value
317372 except click .Abort :
@@ -333,11 +388,13 @@ def add(server_name, force=False, alias=None, target: str | None = None):
333388 # replace arguments with values
334389 processed_args = []
335390 for arg in install_args :
336- matched = re .match (r"\$\{(.*?)\}" , arg )
391+ # check if the argument contains a variable
392+ matched = re .search (r"\$\{([^}]+)\}" , arg )
337393 if matched :
338394 original , key = matched .group (0 ), matched .group (1 )
339395 if key not in processed_variables :
340- # not required, remove the argument from args
396+ # Keep the original argument if variable not found
397+ processed_args .append (arg )
341398 continue
342399 replaced_arg = arg .replace (original , processed_variables .get (key , arg ))
343400 processed_args .append (replaced_arg )
@@ -391,3 +448,15 @@ def add(server_name, force=False, alias=None, target: str | None = None):
391448 console .print (f' Try: [italic]"{ prompt } "[/]\n ' )
392449 else :
393450 console .print (f"[bold red]Failed to add { server_name } to { target_name } .[/]" )
451+
452+
453+ def _should_hide_input (arg_name : str ) -> bool :
454+ """Determine if input should be hidden for a given argument name.
455+
456+ Args:
457+ arg_name: The name of the argument to check
458+
459+ Returns:
460+ bool: True if input should be hidden, False otherwise
461+ """
462+ return "token" in arg_name .lower () or "key" in arg_name .lower () or "secret" in arg_name .lower ()
0 commit comments