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
2 changes: 1 addition & 1 deletion cycode/cli/apps/ai_guardrails/install_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
6 changes: 3 additions & 3 deletions cycode/cli/apps/ai_guardrails/scan/payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
7 changes: 2 additions & 5 deletions cycode/cli/apps/ai_guardrails/scan/response_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
2 changes: 1 addition & 1 deletion cycode/cli/apps/ai_guardrails/scan/scan_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion cycode/cli/apps/ai_guardrails/status_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion cycode/cli/apps/ai_guardrails/uninstall_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
31 changes: 31 additions & 0 deletions tests/cli/commands/ai_guardrails/scan/test_scan_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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}'