diff --git a/features/resource_cli_git_url_flag.feature b/features/resource_cli_git_url_flag.feature new file mode 100644 index 000000000..45fda0190 --- /dev/null +++ b/features/resource_cli_git_url_flag.feature @@ -0,0 +1,14 @@ +Feature: Git resource CLI supports URL flag + Background: + Given a fresh in-memory resource registry + And built-in types are bootstrapped + + Scenario: Adding a git resource with only a URL + When I run resource add "git" "local/upstream" with url "git@github.com:org/upstream.git" + Then the resource command should succeed + And the resource registry resource "local/upstream" should have property "url" equal to "git@github.com:org/upstream.git" + + Scenario: Using --url with a non-git resource type is rejected + When I run resource add type "fs-directory" name "local/data" path "/tmp/data" url "git@github.com:org/upstream.git" + Then the resource command should fail + And the resource output should contain "--url is only valid for git resources" diff --git a/features/steps/resource_cli_steps.py b/features/steps/resource_cli_steps.py index 5bf3ac3a6..cbd6c2521 100644 --- a/features/steps/resource_cli_steps.py +++ b/features/steps/resource_cli_steps.py @@ -254,7 +254,23 @@ def step_run_type_remove(context: Context, name: str) -> None: # ---- Resource Add ---- -def _do_resource_add(context: Context, type_name: str, name: str, path: str) -> None: +def _do_resource_add( + context: Context, + type_name: str, + name: str, + path: str | None, + *, + branch: str | None = None, + description: str | None = None, + image: str | None = None, + container_id: str | None = None, + mount: list[str] | None = None, + url: str | None = None, + clone_into: str | None = None, + read_only: bool = False, + update: bool = False, + fmt: str = "rich", +) -> None: """Run resource add command (shared impl).""" from cleveragents.cli.commands.resource import resource_add @@ -265,12 +281,16 @@ def _do_resource_add(context: Context, type_name: str, name: str, path: str) -> type_name=type_name, name=name, path=path, - branch=None, - description=None, - read_only=False, - clone_into=None, - update=False, - fmt="rich", + branch=branch, + description=description, + image=image, + container_id=container_id, + mount=mount, + url=url, + clone_into=clone_into, + read_only=read_only, + update=update, + fmt=fmt, ) context.resource_cli_output = output context.resource_cli_failed = failed @@ -294,6 +314,24 @@ def step_given_resource_add( _do_resource_add(context, type_name, name, path) +@when('I run resource add "{type_name}" "{name}" with url "{url_value}"') +def step_run_resource_add_url( + context: Context, type_name: str, name: str, url_value: str +) -> None: + """Run resource add for a resource using only --url.""" + _do_resource_add(context, type_name, name, None, url=url_value) + + +@when( + 'I run resource add type "{type_name}" name "{name}" path "{path}" url "{url_value}"' +) +def step_run_resource_add_path_url( + context: Context, type_name: str, name: str, path: str, url_value: str +) -> None: + """Run resource add providing both path and url flags.""" + _do_resource_add(context, type_name, name, path, url=url_value) + + # ---- Resource List ---- @@ -401,12 +439,36 @@ def step_resource_output_contains(context: Context, text: str) -> None: ) +@then("the resource command should succeed") +def step_resource_command_succeeded(context: Context) -> None: + """Ensure the resource CLI command completed successfully.""" + failed = getattr(context, "resource_cli_failed", False) + assert not failed, "Expected command to succeed but it failed" + + @then("the resource command should fail") def step_resource_command_failed(context: Context) -> None: """Check that the command failed.""" assert context.resource_cli_failed, "Expected command to fail but it succeeded" +@then( + 'the resource registry resource "{resource_name}" should have property ' + '"{property_name}" equal to "{expected_value}"' +) +def step_resource_property_equals( + context: Context, resource_name: str, property_name: str, expected_value: str +) -> None: + """Assert that the stored resource has the expected property value.""" + service = _make_service(context) + resource = service.show_resource(resource_name) + properties = resource.properties or {} + actual_value = properties.get(property_name) + assert ( + actual_value == expected_value + ), f"Expected property '{property_name}' to be '{expected_value}', got '{actual_value}'" + + @then("the resource output should be valid JSON") def step_resource_output_valid_json(context: Context) -> None: """Check that the output is valid JSON.""" diff --git a/src/cleveragents/cli/commands/resource.py b/src/cleveragents/cli/commands/resource.py index a9394cdf9..6765e7d18 100644 --- a/src/cleveragents/cli/commands/resource.py +++ b/src/cleveragents/cli/commands/resource.py @@ -586,6 +586,13 @@ def resource_add( str | None, typer.Option("--branch", "-b", help="Branch for git-checkout resources"), ] = None, + url: Annotated[ + str | None, + typer.Option( + "--url", + help="Remote URL for git resources (required for remote-only repositories)", + ), + ] = None, description: Annotated[ str | None, typer.Option("--description", "-d", help="Resource description"), @@ -689,6 +696,22 @@ def resource_add( ) raise typer.Abort() + if url is not None: + if type_name != "git": + console.print( + "[red]Validation error:[/red] --url is only valid for git resources." + ) + raise typer.Abort() + url_value = url.strip() + if not url_value: + console.print( + "[red]Validation error:[/red] --url must not be empty." + ) + raise typer.Abort() + else: + url_value = None + + # Validate --container-id usage rules. if container_id is not None and image is not None: console.print( @@ -747,6 +770,8 @@ def resource_add( properties["path"] = path if branch is not None: properties["branch"] = branch + if url_value is not None: + properties["url"] = url_value if image is not None: properties["image"] = image if container_id is not None: