diff --git a/cycode/cli/apps/ai_guardrails/install_command.py b/cycode/cli/apps/ai_guardrails/install_command.py index 882ebad4..a72d5d4c 100644 --- a/cycode/cli/apps/ai_guardrails/install_command.py +++ b/cycode/cli/apps/ai_guardrails/install_command.py @@ -31,7 +31,7 @@ def install_command( '--ide', help='IDE to install hooks for (e.g., "cursor", "claude-code", or "all" for all IDEs). Defaults to cursor.', ), - ] = AIIDEType.CURSOR, + ] = AIIDEType.CURSOR.value, repo_path: Annotated[ Optional[Path], typer.Option( diff --git a/cycode/cli/apps/ai_guardrails/scan/payload.py b/cycode/cli/apps/ai_guardrails/scan/payload.py index ce72a574..08e96f9a 100644 --- a/cycode/cli/apps/ai_guardrails/scan/payload.py +++ b/cycode/cli/apps/ai_guardrails/scan/payload.py @@ -155,7 +155,7 @@ def from_cursor_payload(cls, payload: dict) -> 'AIHookPayload': generation_id=payload.get('generation_id'), ide_user_email=payload.get('user_email'), model=payload.get('model'), - ide_provider=AIIDEType.CURSOR, + ide_provider=AIIDEType.CURSOR.value, ide_version=payload.get('cursor_version'), prompt=payload.get('prompt', ''), file_path=payload.get('file_path') or payload.get('path'), @@ -213,7 +213,7 @@ def from_claude_code_payload(cls, payload: dict) -> 'AIHookPayload': generation_id=generation_id, ide_user_email=None, # Claude Code doesn't provide this in hook payload model=model, - ide_provider=AIIDEType.CLAUDE_CODE, + ide_provider=AIIDEType.CLAUDE_CODE.value, ide_version=ide_version, prompt=payload.get('prompt', ''), file_path=file_path, @@ -248,7 +248,7 @@ def is_payload_for_ide(payload: dict, ide: str) -> bool: return True @classmethod - def from_payload(cls, payload: dict, tool: str = AIIDEType.CURSOR) -> 'AIHookPayload': + def from_payload(cls, payload: dict, tool: str = AIIDEType.CURSOR.value) -> 'AIHookPayload': """Create AIHookPayload from any tool's payload. Args: diff --git a/cycode/cli/apps/ai_guardrails/scan/response_builders.py b/cycode/cli/apps/ai_guardrails/scan/response_builders.py index f0da71b7..ff0a6aa4 100644 --- a/cycode/cli/apps/ai_guardrails/scan/response_builders.py +++ b/cycode/cli/apps/ai_guardrails/scan/response_builders.py @@ -117,7 +117,7 @@ def deny_prompt(self, user_message: str) -> dict: } -def get_response_builder(ide: str = AIIDEType.CURSOR) -> IDEResponseBuilder: +def get_response_builder(ide: str = AIIDEType.CURSOR.value) -> IDEResponseBuilder: """Get the response builder for a specific IDE. Args: @@ -129,10 +129,7 @@ def get_response_builder(ide: str = AIIDEType.CURSOR) -> IDEResponseBuilder: Raises: ValueError: If the IDE is not supported """ - # Normalize to AIIDEType if string passed - if isinstance(ide, str): - ide = ide.lower() - builder = _RESPONSE_BUILDERS.get(ide) + builder = _RESPONSE_BUILDERS.get(ide.lower()) if not builder: raise ValueError(f'Unsupported IDE: {ide}. Supported IDEs: {list(_RESPONSE_BUILDERS.keys())}') return builder diff --git a/cycode/cli/apps/ai_guardrails/scan/scan_command.py b/cycode/cli/apps/ai_guardrails/scan/scan_command.py index 288b0025..fe1c74a3 100644 --- a/cycode/cli/apps/ai_guardrails/scan/scan_command.py +++ b/cycode/cli/apps/ai_guardrails/scan/scan_command.py @@ -69,7 +69,7 @@ def scan_command( help='IDE that sent the payload (e.g., "cursor"). Defaults to cursor.', hidden=True, ), - ] = AIIDEType.CURSOR, + ] = AIIDEType.CURSOR.value, ) -> None: """Scan content from AI IDE hooks for secrets. diff --git a/cycode/cli/apps/ai_guardrails/status_command.py b/cycode/cli/apps/ai_guardrails/status_command.py index 0808d806..ee1e5bcf 100644 --- a/cycode/cli/apps/ai_guardrails/status_command.py +++ b/cycode/cli/apps/ai_guardrails/status_command.py @@ -28,7 +28,7 @@ def status_command( '--ide', help='IDE to check status for (e.g., "cursor", "claude-code", or "all" for all IDEs). Defaults to cursor.', ), - ] = AIIDEType.CURSOR, + ] = AIIDEType.CURSOR.value, repo_path: Annotated[ Optional[Path], typer.Option( diff --git a/cycode/cli/apps/ai_guardrails/uninstall_command.py b/cycode/cli/apps/ai_guardrails/uninstall_command.py index be4288e3..f7b8341c 100644 --- a/cycode/cli/apps/ai_guardrails/uninstall_command.py +++ b/cycode/cli/apps/ai_guardrails/uninstall_command.py @@ -31,7 +31,7 @@ def uninstall_command( '--ide', help='IDE to uninstall hooks from (e.g., "cursor", "claude-code", "all"). Defaults to cursor.', ), - ] = AIIDEType.CURSOR, + ] = AIIDEType.CURSOR.value, repo_path: Annotated[ Optional[Path], typer.Option( diff --git a/tests/cli/commands/ai_guardrails/scan/test_scan_command.py b/tests/cli/commands/ai_guardrails/scan/test_scan_command.py index d1473c69..4bcb35f2 100644 --- a/tests/cli/commands/ai_guardrails/scan/test_scan_command.py +++ b/tests/cli/commands/ai_guardrails/scan/test_scan_command.py @@ -6,7 +6,9 @@ import pytest from pytest_mock import MockerFixture +from typer.testing import CliRunner +from cycode.cli.apps.ai_guardrails import app as ai_guardrails_app from cycode.cli.apps.ai_guardrails.scan.scan_command import scan_command @@ -136,3 +138,32 @@ def test_claude_code_payload_with_claude_code_ide( mock_scan_command_deps['load_policy'].assert_called_once() mock_scan_command_deps['get_handler'].assert_called_once() mock_handler.assert_called_once() + + +class TestDefaultIdeParameterViaCli: + """Tests that verify default IDE parameter works correctly via CLI invocation.""" + + def test_scan_command_default_ide_via_cli(self, mocker: MockerFixture) -> None: + """Test scan_command works with default --ide when invoked via CLI. + + This test catches issues where Typer converts enum defaults to strings + incorrectly (e.g., AIIDEType.CURSOR becomes 'AIIDEType.CURSOR' instead of 'cursor'). + """ + mocker.patch('cycode.cli.apps.ai_guardrails.scan.scan_command._initialize_clients') + mocker.patch( + 'cycode.cli.apps.ai_guardrails.scan.scan_command.load_policy', + return_value={'fail_open': True}, + ) + mock_handler = MagicMock(return_value={'continue': True}) + mocker.patch( + 'cycode.cli.apps.ai_guardrails.scan.scan_command.get_handler_for_event', + return_value=mock_handler, + ) + + runner = CliRunner() + payload = json.dumps({'hook_event_name': 'beforeSubmitPrompt', 'prompt': 'test'}) + + # Invoke via CLI without --ide flag to use default + result = runner.invoke(ai_guardrails_app, ['scan'], input=payload) + + assert result.exit_code == 0, f'Command failed: {result.output}'