diff --git a/src/uipath/_cli/cli_init.py b/src/uipath/_cli/cli_init.py index 4f93a12af..533666c89 100644 --- a/src/uipath/_cli/cli_init.py +++ b/src/uipath/_cli/cli_init.py @@ -60,18 +60,25 @@ def generate_env_file(target_directory): console.success(f"Created '{relative_path}' file.") -def generate_agent_md_file(target_directory: str, file_name: str) -> None: +def generate_agent_md_file( + target_directory: str, file_name: str, no_agents_md_override: bool +) -> bool: """Generate an agent-specific file from the packaged resource. Args: target_directory: The directory where the file should be created. file_name: The name of the file should be created. + no_agents_md_override: Whether to override existing files. """ target_path = os.path.join(target_directory, file_name) - if os.path.exists(target_path): - logger.debug(f"File '{target_path}' already exists.") - return + will_override = os.path.exists(target_path) + + if will_override and no_agents_md_override: + console.success( + f"File {click.style(target_path, fg='cyan')} already exists. Skipping." + ) + return False try: source_path = importlib.resources.files("uipath._resources").joinpath(file_name) @@ -79,15 +86,23 @@ def generate_agent_md_file(target_directory: str, file_name: str) -> None: with importlib.resources.as_file(source_path) as s_path: shutil.copy(s_path, target_path) + if will_override: + logger.debug(f"File '{target_path}' has been overridden.") + + return will_override + except Exception as e: console.warning(f"Could not create {file_name}: {e}") + return False + -def generate_agent_md_files(target_directory: str) -> None: +def generate_agent_md_files(target_directory: str, no_agents_md_override: bool) -> None: """Generate an agent-specific file from the packaged resource. Args: target_directory: The directory where the files should be created. + no_agents_md_override: Whether to override existing files. """ agent_dir = os.path.join(target_directory, ".agent") os.makedirs(agent_dir, exist_ok=True) @@ -96,13 +111,21 @@ def generate_agent_md_files(target_directory: str) -> None: agent_files = ["CLI_REFERENCE.md", "REQUIRED_STRUCTURE.md", "SDK_REFERENCE.md"] + any_overridden = False + for file_name in root_files: - generate_agent_md_file(target_directory, file_name) + if generate_agent_md_file(target_directory, file_name, no_agents_md_override): + any_overridden = True for file_name in agent_files: - generate_agent_md_file(agent_dir, file_name) + if generate_agent_md_file(agent_dir, file_name, no_agents_md_override): + any_overridden = True - console.success(f"Created {click.style('AGENTS.md', fg='cyan')} file.") + if any_overridden: + console.success(f"Updated {click.style('AGENTS.md', fg='cyan')} related files.") + return + + console.success(f"Created {click.style('AGENTS.md', fg='cyan')} related files.") def get_existing_settings(config_path: str) -> Optional[Dict[str, Any]]: @@ -149,8 +172,15 @@ def write_config_file(config_data: Dict[str, Any] | RuntimeSchema) -> None: default=True, help="Infer bindings from the script.", ) +@click.option( + "--no-agents-md-override", + is_flag=True, + required=False, + default=False, + help="Won't override existing .agent files and AGENTS.md file.", +) @track -def init(entrypoint: str, infer_bindings: bool) -> None: +def init(entrypoint: str, infer_bindings: bool, no_agents_md_override: bool) -> None: """Create uipath.json with input/output schemas and bindings.""" with console.spinner("Initializing UiPath project ..."): current_directory = os.getcwd() @@ -175,7 +205,7 @@ def init(entrypoint: str, infer_bindings: bool) -> None: if not result.should_continue: return - generate_agent_md_files(current_directory) + generate_agent_md_files(current_directory, no_agents_md_override) script_path = get_user_script(current_directory, entrypoint=entrypoint) if not script_path: return diff --git a/src/uipath/_resources/CLI_REFERENCE.md b/src/uipath/_resources/CLI_REFERENCE.md index 0515fb56f..c87640068 100644 --- a/src/uipath/_resources/CLI_REFERENCE.md +++ b/src/uipath/_resources/CLI_REFERENCE.md @@ -27,6 +27,7 @@ The UiPath Python SDK provides a comprehensive CLI for managing coded agents and | Option | Type | Default | Description | |--------|------|---------|-------------| | `--infer-bindings` | flag | false | Infer bindings from the script. | +| `--no-agents-md-override` | flag | false | Won't override existing .agent files and AGENTS.md file. | **Usage Examples:** diff --git a/tests/cli/test_init_agents_md.py b/tests/cli/test_init_agents_md.py index 12b9b70ad..718de1de3 100644 --- a/tests/cli/test_init_agents_md.py +++ b/tests/cli/test_init_agents_md.py @@ -44,22 +44,42 @@ def test_generate_agent_md_file_creates_file(self) -> None: mock_as_file.return_value.__enter__.return_value = mock_source mock_as_file.return_value.__exit__.return_value = None - generate_agent_md_file(temp_dir, "AGENTS.md") + generate_agent_md_file(temp_dir, "AGENTS.md", False) assert (Path(temp_dir) / "AGENTS.md").exists() finally: os.chdir(original_cwd) - def test_generate_agents_md_skips_existing_file(self) -> None: - """Test that existing AGENTS.md is not overwritten.""" + def test_generate_agents_md_overwrites_existing_file(self) -> None: + """Test that existing AGENTS.md is overwritten.""" with tempfile.TemporaryDirectory() as temp_dir: agents_path = Path(temp_dir) / "AGENTS.md" original_content = "Original content" agents_path.write_text(original_content) - generate_agent_md_file(temp_dir, "AGENTS.md") + mock_source = ( + Path(__file__).parent.parent.parent + / "src" + / "uipath" + / "_resources" + / "AGENTS.md" + ) - assert agents_path.read_text() == original_content + with ( + patch("uipath._cli.cli_init.importlib.resources.files") as mock_files, + patch( + "uipath._cli.cli_init.importlib.resources.as_file" + ) as mock_as_file, + ): + mock_path = MagicMock() + mock_files.return_value.joinpath.return_value = mock_path + mock_as_file.return_value.__enter__.return_value = mock_source + mock_as_file.return_value.__exit__.return_value = None + + generate_agent_md_file(temp_dir, "AGENTS.md", False) + + assert agents_path.read_text() != original_content + assert agents_path.exists() def test_generate_agents_md_handles_errors_gracefully(self) -> None: """Test that errors are handled gracefully.""" @@ -70,7 +90,7 @@ def test_generate_agents_md_handles_errors_gracefully(self) -> None: ): mock_files.side_effect = RuntimeError("Test error") - generate_agent_md_file(temp_dir, "AGENTS.md") + generate_agent_md_file(temp_dir, "AGENTS.md", False) mock_console.warning.assert_called_once() assert "Could not create AGENTS.md: Test error" in str( @@ -100,7 +120,7 @@ def test_generate_agent_md_files_creates_all_files(self) -> None: mock_as_file.return_value.__enter__.return_value = temp_source mock_as_file.return_value.__exit__.return_value = None - generate_agent_md_files(temp_dir) + generate_agent_md_files(temp_dir, False) agent_dir = Path(temp_dir) / ".agent" assert agent_dir.exists() @@ -113,8 +133,8 @@ def test_generate_agent_md_files_creates_all_files(self) -> None: assert (agent_dir / "REQUIRED_STRUCTURE.md").exists() assert (agent_dir / "SDK_REFERENCE.md").exists() - def test_generate_agent_md_files_skips_existing_files(self) -> None: - """Test that existing files are not overwritten.""" + def test_generate_agent_md_files_overwrites_existing_files(self) -> None: + """Test that existing files are overwritten.""" with tempfile.TemporaryDirectory() as temp_dir: agent_dir = Path(temp_dir) / ".agent" agent_dir.mkdir() @@ -132,6 +152,7 @@ def test_generate_agent_md_files_skips_existing_files(self) -> None: patch( "uipath._cli.cli_init.importlib.resources.as_file" ) as mock_as_file, + patch("uipath._cli.cli_init.console"), ): temp_source = Path(temp_dir) / "temp_source.md" temp_source.write_text("Test content") @@ -141,10 +162,12 @@ def test_generate_agent_md_files_skips_existing_files(self) -> None: mock_as_file.return_value.__enter__.return_value = temp_source mock_as_file.return_value.__exit__.return_value = None - generate_agent_md_files(temp_dir) + generate_agent_md_files(temp_dir, False) - assert agents_path.read_text() == agents_content - assert cli_ref_path.read_text() == cli_ref_content + assert agents_path.read_text() != agents_content + assert agents_path.read_text() == "Test content" + assert cli_ref_path.read_text() != cli_ref_content + assert cli_ref_path.read_text() == "Test content" class TestInitWithAgentsMd: @@ -187,10 +210,10 @@ def test_init_creates_agent_files_by_default( assert os.path.exists(".agent/REQUIRED_STRUCTURE.md") assert os.path.exists(".agent/SDK_REFERENCE.md") - def test_init_does_not_overwrite_existing_agents_md( + def test_init_overwrites_existing_agents_md( self, runner: CliRunner, temp_dir: str ) -> None: - """Test that existing AGENTS.md is not overwritten.""" + """Test that existing AGENTS.md is overwritten.""" with runner.isolated_filesystem(temp_dir=temp_dir): # Create a simple Python file with open("main.py", "w") as f: @@ -201,11 +224,28 @@ def test_init_does_not_overwrite_existing_agents_md( with open("AGENTS.md", "w") as f: f.write(original_content) - # Run init (AGENTS.md creation is now default) - result = runner.invoke(cli, ["init"]) + temp_source = Path(temp_dir) / "temp_source.md" + temp_source.write_text("Test content") - assert result.exit_code == 0 + with ( + patch("uipath._cli.cli_init.importlib.resources.files") as mock_files, + patch( + "uipath._cli.cli_init.importlib.resources.as_file" + ) as mock_as_file, + ): + # Setup mocks + mock_path = MagicMock() + mock_files.return_value.joinpath.return_value = mock_path + mock_as_file.return_value.__enter__.return_value = temp_source + mock_as_file.return_value.__exit__.return_value = None + + # Run init (AGENTS.md creation is now default) + result = runner.invoke(cli, ["init"]) + + assert result.exit_code == 0 - # Verify content wasn't changed - with open("AGENTS.md", "r") as f: - assert f.read() == original_content + # Verify content was changed + with open("AGENTS.md", "r") as f: + content = f.read() + assert content != original_content + assert content == "Test content"