Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 40 additions & 10 deletions src/uipath/_cli/cli_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,34 +60,49 @@ 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)

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)
Expand All @@ -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]]:
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/uipath/_resources/CLI_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:**

Expand Down
80 changes: 60 additions & 20 deletions tests/cli/test_init_agents_md.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand All @@ -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(
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand All @@ -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")
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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"
Loading