1515from ...operations .runtime import (
1616 configure_bedrock_agentcore ,
1717 destroy_bedrock_agentcore ,
18+ detect_entrypoint ,
19+ detect_requirements ,
20+ get_relative_path ,
1821 get_status ,
22+ infer_agent_name ,
1923 invoke_bedrock_agentcore ,
2024 launch_bedrock_agentcore ,
2125 validate_agent_name ,
2226)
2327from ...utils .runtime .config import load_config
24- from ...utils .runtime .entrypoint import parse_entrypoint
2528from ...utils .runtime .logs import get_agent_log_paths , get_aws_tail_commands , get_genai_observability_url
2629from ..common import _handle_error , _print_success , console
2730from .configuration_manager import ConfigurationManager
@@ -47,22 +50,50 @@ def _show_configuration_not_found_panel():
4750
4851
4952def _validate_requirements_file (file_path : str ) -> str :
50- """Validate requirements file and return the path."""
53+ """Validate requirements file and return the absolute path."""
5154 from ...utils .runtime .entrypoint import validate_requirements_file
5255
5356 try :
5457 deps = validate_requirements_file (Path .cwd (), file_path )
55- _print_success (f"Using requirements file: [dim]{ deps .resolved_path } [/dim]" )
56- return file_path
58+ rel_path = get_relative_path (Path (deps .resolved_path ))
59+ _print_success (f"Using requirements file: [dim]{ rel_path } [/dim]" )
60+ # Return absolute path for consistency with entrypoint handling
61+ return str (Path (deps .resolved_path ).resolve ())
5762 except (FileNotFoundError , ValueError ) as e :
5863 _handle_error (str (e ), e )
5964
6065
61- def _prompt_for_requirements_file (prompt_text : str , default : str = "" ) -> Optional [str ]:
62- """Prompt user for requirements file path with validation."""
63- response = prompt (prompt_text , completer = PathCompleter (), default = default )
66+ def _prompt_for_requirements_file (prompt_text : str , source_path : str , default : str = "" ) -> Optional [str ]:
67+ """Prompt user for requirements file path with validation.
68+
69+ Args:
70+ prompt_text: Prompt message to display
71+ source_path: Source directory path for validation
72+ default: Default path to pre-populate
73+ """
74+ # Pre-populate with relative source directory path if no default provided
75+ if not default :
76+ rel_source = get_relative_path (Path (source_path ))
77+ default = f"{ rel_source } /"
78+
79+ # Use PathCompleter without filter - allow navigation anywhere
80+ response = prompt (prompt_text , completer = PathCompleter (), complete_while_typing = True , default = default )
6481
6582 if response .strip ():
83+ # Validate file exists and is in source directory
84+ req_file = Path (response .strip ()).resolve ()
85+ source_dir = Path (source_path ).resolve ()
86+
87+ # Check if requirements file is within source directory
88+ try :
89+ if not req_file .is_relative_to (source_dir ):
90+ rel_source = get_relative_path (source_dir )
91+ console .print (f"[red]Error: Requirements file must be in source directory: { rel_source } [/red]" )
92+ return _prompt_for_requirements_file (prompt_text , source_path , default )
93+ except (ValueError , AttributeError ):
94+ # is_relative_to not available or other error - skip validation
95+ pass
96+
6697 return _validate_requirements_file (response .strip ())
6798
6899 return None
@@ -76,56 +107,75 @@ def _handle_requirements_file_display(
76107 Args:
77108 requirements_file: Explicit requirements file path
78109 non_interactive: Whether to skip interactive prompts
79- source_path: Optional source code directory (checks here first, then falls back to project root)
110+ source_path: Optional source code directory
80111 """
81- from ...utils .runtime .entrypoint import detect_dependencies
82-
83112 if requirements_file :
84113 # User provided file - validate and show confirmation
85114 return _validate_requirements_file (requirements_file )
86115
87- # Detect dependencies:
88- # - If source_path provided: check source_path only
89- # - Otherwise: check project root (Path.cwd())
90- if source_path :
91- source_dir = Path (source_path )
92- deps = detect_dependencies (source_dir )
93- else :
94- # No source_path, check project root
95- deps = detect_dependencies (Path .cwd ())
116+ # Use operations layer for detection - source_path is always provided
117+ deps = detect_requirements (Path (source_path ))
96118
97119 if non_interactive :
98120 # Auto-detection for non-interactive mode
99121 if deps .found :
100- _print_success (f"Using detected file: [dim]{ deps .file } [/dim]" )
122+ rel_deps_path = get_relative_path (Path (deps .resolved_path ))
123+ _print_success (f"Using detected requirements file: [cyan]{ rel_deps_path } [/cyan]" )
101124 return None # Use detected file
102125 else :
103126 _handle_error ("No requirements file specified and none found automatically" )
104127
105128 # Auto-detection with interactive prompt
106129 if deps .found :
107- console .print (f"\n 🔍 [cyan]Detected dependency file:[/cyan] [bold]{ deps .file } [/bold]" )
130+ rel_deps_path = get_relative_path (Path (deps .resolved_path ))
131+
132+ console .print (f"\n 🔍 [cyan]Detected dependency file:[/cyan] [bold]{ rel_deps_path } [/bold]" )
108133 console .print ("[dim]Press Enter to use this file, or type a different path (use Tab for autocomplete):[/dim]" )
109134
110- result = _prompt_for_requirements_file ("Path or Press Enter to use detected dependency file: " , default = "" )
135+ result = _prompt_for_requirements_file (
136+ "Path or Press Enter to use detected dependency file: " , source_path = source_path , default = rel_deps_path
137+ )
111138
112139 if result is None :
113140 # Use detected file
114- _print_success (f"Using detected file: [dim] { deps . file } [/dim ]" )
141+ _print_success (f"Using detected requirements file: [cyan] { rel_deps_path } [/cyan ]" )
115142
116143 return result
117144 else :
118145 console .print ("\n [yellow]⚠️ No dependency file found (requirements.txt or pyproject.toml)[/yellow]" )
119146 console .print ("[dim]Enter path to requirements file (use Tab for autocomplete), or press Enter to skip:[/dim]" )
120147
121- result = _prompt_for_requirements_file ("Path: " )
148+ result = _prompt_for_requirements_file ("Path: " , source_path = source_path )
122149
123150 if result is None :
124151 _handle_error ("No requirements file specified and none found automatically" )
125152
126153 return result
127154
128155
156+ def _detect_entrypoint_in_source (source_path : str , non_interactive : bool = False ) -> str :
157+ """Detect entrypoint file in source directory with CLI display."""
158+ source_dir = Path (source_path )
159+
160+ # Use operations layer for detection
161+ detected = detect_entrypoint (source_dir )
162+
163+ if not detected :
164+ # No fallback prompt - fail with clear error message
165+ rel_source = get_relative_path (source_dir )
166+ _handle_error (
167+ f"No entrypoint file found in { rel_source } \n "
168+ f"Expected one of: main.py, agent.py, app.py, __main__.py\n "
169+ f"Please specify full file path (e.g., { rel_source } /your_agent.py)"
170+ )
171+
172+ # Show detection and confirm
173+ rel_entrypoint = get_relative_path (detected )
174+
175+ _print_success (f"Using entrypoint file: [cyan]{ rel_entrypoint } [/cyan]" )
176+ return str (detected )
177+
178+
129179# Define options at module level to avoid B008
130180ENV_OPTION = typer .Option (None , "--env" , "-env" , help = "Environment variables for local mode (format: KEY=VALUE)" )
131181
@@ -179,7 +229,12 @@ def set_default(name: str = typer.Argument(...)):
179229@configure_app .callback (invoke_without_command = True )
180230def configure (
181231 ctx : typer .Context ,
182- entrypoint : Optional [str ] = typer .Option (None , "--entrypoint" , "-e" , help = "Python file with BedrockAgentCoreApp" ),
232+ entrypoint : Optional [str ] = typer .Option (
233+ None ,
234+ "--entrypoint" ,
235+ "-e" ,
236+ help = "Entry point: file path (e.g., agent.py) or directory path (auto-detects main.py, agent.py, app.py)" ,
237+ ),
183238 agent_name : Optional [str ] = typer .Option (None , "--name" , "-n" ),
184239 execution_role : Optional [str ] = typer .Option (None , "--execution-role" , "-er" ),
185240 code_build_execution_role : Optional [str ] = typer .Option (None , "--code-build-execution-role" , "-cber" ),
@@ -206,35 +261,75 @@ def configure(
206261 non_interactive : bool = typer .Option (
207262 False , "--non-interactive" , "-ni" , help = "Skip prompts; use defaults unless overridden"
208263 ),
209- source_path : Optional [str ] = typer .Option (None , "--source-path" , "-sp" , help = "Path to agent source code directory" ),
210264):
211- """Configure a Bedrock AgentCore agent. The agent name defaults to your Python file name."""
265+ """Configure a Bedrock AgentCore agent interactively or with parameters.
266+
267+ Examples:
268+ agentcore configure # Fully interactive (current directory)
269+ agentcore configure --entrypoint writer/ # Directory (auto-detect entrypoint)
270+ agentcore configure --entrypoint agent.py # File (use as entrypoint)
271+ """
212272 if ctx .invoked_subcommand is not None :
213273 return
214274
215- if not entrypoint :
216- _handle_error ("--entrypoint is required" )
217-
218275 if protocol and protocol .upper () not in ["HTTP" , "MCP" , "A2A" ]:
219276 _handle_error ("Error: --protocol must be either HTTP or MCP or A2A" )
220277
221278 console .print ("[cyan]Configuring Bedrock AgentCore...[/cyan]" )
222- try :
223- _ , file_name = parse_entrypoint (entrypoint )
224- agent_name = agent_name or file_name
225-
226- valid , error = validate_agent_name (agent_name )
227- if not valid :
228- _handle_error (error )
229-
230- console .print (f"[dim]Agent name: { agent_name } [/dim]" )
231- except ValueError as e :
232- _handle_error (f"Error: { e } " , e )
233279
234- # Create configuration manager for clean, elegant prompting
280+ # Create configuration manager early for consistent prompting
235281 config_path = Path .cwd () / ".bedrock_agentcore.yaml"
236282 config_manager = ConfigurationManager (config_path , non_interactive )
237283
284+ # Interactive entrypoint selection
285+ if not entrypoint :
286+ if non_interactive :
287+ entrypoint_input = "."
288+ else :
289+ console .print ("\n 📂 [cyan]Entrypoint Selection[/cyan]" )
290+ console .print ("[dim]Specify the entry point (use Tab for autocomplete):[/dim]" )
291+ console .print ("[dim] • File path: weather/agent.py[/dim]" )
292+ console .print ("[dim] • Directory: weather/ (auto-detects main.py, agent.py, app.py)[/dim]" )
293+ console .print ("[dim] • Current directory: press Enter[/dim]" )
294+
295+ entrypoint_input = (
296+ prompt ("Entrypoint: " , completer = PathCompleter (), complete_while_typing = True , default = "" ).strip () or "."
297+ )
298+ else :
299+ entrypoint_input = entrypoint
300+
301+ # Resolve the entrypoint_input (handles both file and directory)
302+ entrypoint_path = Path (entrypoint_input ).resolve ()
303+
304+ if entrypoint_path .is_file ():
305+ # It's a file - use directly as entrypoint
306+ entrypoint = str (entrypoint_path )
307+ source_path = str (entrypoint_path .parent )
308+ if not non_interactive :
309+ rel_path = get_relative_path (entrypoint_path )
310+ _print_success (f"Using file: { rel_path } " )
311+ elif entrypoint_path .is_dir ():
312+ # It's a directory - detect entrypoint within it
313+ source_path = str (entrypoint_path )
314+ entrypoint = _detect_entrypoint_in_source (source_path , non_interactive )
315+ else :
316+ _handle_error (f"Path not found: { entrypoint_input } " )
317+
318+ # Process agent name
319+ entrypoint_path = Path (entrypoint )
320+
321+ # Infer agent name from full entrypoint path (e.g., agents/writer/main.py -> agents_writer_main)
322+ if not agent_name :
323+ suggested_name = infer_agent_name (entrypoint_path )
324+ agent_name = config_manager .prompt_agent_name (suggested_name )
325+
326+ valid , error = validate_agent_name (agent_name )
327+ if not valid :
328+ _handle_error (error )
329+
330+ # Handle dependency file selection with simplified logic
331+ final_requirements_file = _handle_requirements_file_display (requirements_file , non_interactive , source_path )
332+
238333 # Interactive prompts for missing values - clean and elegant
239334 if not execution_role :
240335 execution_role = config_manager .prompt_execution_role ()
@@ -253,9 +348,6 @@ def configure(
253348 auto_create_ecr = False
254349 _print_success (f"Using existing ECR repository: [dim]{ ecr_repository } [/dim]" )
255350
256- # Handle dependency file selection with simplified logic
257- final_requirements_file = _handle_requirements_file_display (requirements_file , non_interactive , source_path )
258-
259351 # Handle OAuth authorization configuration
260352 oauth_config = None
261353 if authorizer_config :
@@ -318,26 +410,26 @@ def configure(
318410 headers = request_header_config .get ("requestHeaderAllowlist" , [])
319411 headers_info = f"Request Headers Allowlist: [dim]{ len (headers )} headers configured[/dim]\n "
320412
413+ execution_role_display = "Auto-create" if not result .execution_role else result .execution_role
321414 memory_info = "Short-term memory (30-day retention)"
322415 if disable_memory :
323416 memory_info = "Disabled"
324417
325418 console .print (
326419 Panel (
327- f"[green]Configuration Complete[/green]\n \n "
328- f"[bold]Agent Details:[/bold]\n "
420+ f"[bold]Agent Details[/bold]\n "
329421 f"Agent Name: [cyan]{ agent_name } [/cyan]\n "
330422 f"Runtime: [cyan]{ result .runtime } [/cyan]\n "
331423 f"Region: [cyan]{ result .region } [/cyan]\n "
332- f"Account: [dim ]{ result .account_id } [/dim ]\n \n "
333- f"[bold]Configuration: [/bold]\n "
334- f"Execution Role: [dim] { result . execution_role } [/dim ]\n "
335- f"ECR Repository: [dim ]"
424+ f"Account: [cyan ]{ result .account_id } [/cyan ]\n \n "
425+ f"[bold]Configuration[/bold]\n "
426+ f"Execution Role: [cyan] { execution_role_display } [/cyan ]\n "
427+ f"ECR Repository: [cyan ]"
336428 f"{ 'Auto-create' if result .auto_create_ecr else result .ecr_repository or 'N/A' } "
337- f"[/dim ]\n "
338- f"Authorization: [dim ]{ auth_info } [/dim ]\n \n "
429+ f"[/cyan ]\n "
430+ f"Authorization: [cyan ]{ auth_info } [/cyan ]\n \n "
339431 f"{ headers_info } \n "
340- f"Memory: [dim ]{ memory_info } [/dim ]\n \n "
432+ f"Memory: [cyan ]{ memory_info } [/cyan ]\n \n "
341433 f"📄 Config saved to: [dim]{ result .config_path } [/dim]\n \n "
342434 f"[bold]Next Steps:[/bold]\n "
343435 f" [cyan]agentcore launch[/cyan]" ,
@@ -501,7 +593,6 @@ def launch(
501593 region = agent_config .aws .region if agent_config else "us-east-1"
502594
503595 deploy_panel = (
504- f"✅ [green]CodeBuild Deployment Successful![/green]\n \n "
505596 f"[bold]Agent Details:[/bold]\n "
506597 f"Agent Name: [cyan]{ agent_name } [/cyan]\n "
507598 f"Agent ARN: [cyan]{ result .agent_arn } [/cyan]\n "
@@ -541,15 +632,12 @@ def launch(
541632
542633 if local_build :
543634 title = "Local Build Success"
544- deployment_type = "✅ [green]Local Build Deployment Successful![/green]"
545635 icon = "🔧"
546636 else :
547637 title = "Deployment Success"
548- deployment_type = "✅ [green]Deployment Successful![/green]"
549638 icon = "🚀"
550639
551640 deploy_panel = (
552- f"{ deployment_type } \n \n "
553641 f"[bold]Agent Details:[/bold]\n "
554642 f"Agent Name: [cyan]{ agent_name } [/cyan]\n "
555643 f"Agent ARN: [cyan]{ result .agent_arn } [/cyan]\n "
0 commit comments