Skip to content

Commit 1c02ec0

Browse files
Ilanlidoclaude
andcommitted
CM-58331 fix AIIDEType enum default values in CLI commands
Typer converts enum defaults to strings using str(enum), which returns 'AIIDEType.CURSOR' instead of 'cursor'. This caused scan command to fail with "Unsupported IDE: aiidetype.cursor". Fixed by using AIIDEType.CURSOR.value instead of AIIDEType.CURSOR for all CLI option defaults and ide_provider assignments. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 71d08c8 commit 1c02ec0

File tree

7 files changed

+40
-12
lines changed

7 files changed

+40
-12
lines changed

cycode/cli/apps/ai_guardrails/install_command.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def install_command(
3131
'--ide',
3232
help='IDE to install hooks for (e.g., "cursor", "claude-code", or "all" for all IDEs). Defaults to cursor.',
3333
),
34-
] = AIIDEType.CURSOR,
34+
] = AIIDEType.CURSOR.value,
3535
repo_path: Annotated[
3636
Optional[Path],
3737
typer.Option(

cycode/cli/apps/ai_guardrails/scan/payload.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def from_cursor_payload(cls, payload: dict) -> 'AIHookPayload':
155155
generation_id=payload.get('generation_id'),
156156
ide_user_email=payload.get('user_email'),
157157
model=payload.get('model'),
158-
ide_provider=AIIDEType.CURSOR,
158+
ide_provider=AIIDEType.CURSOR.value,
159159
ide_version=payload.get('cursor_version'),
160160
prompt=payload.get('prompt', ''),
161161
file_path=payload.get('file_path') or payload.get('path'),
@@ -213,7 +213,7 @@ def from_claude_code_payload(cls, payload: dict) -> 'AIHookPayload':
213213
generation_id=generation_id,
214214
ide_user_email=None, # Claude Code doesn't provide this in hook payload
215215
model=model,
216-
ide_provider=AIIDEType.CLAUDE_CODE,
216+
ide_provider=AIIDEType.CLAUDE_CODE.value,
217217
ide_version=ide_version,
218218
prompt=payload.get('prompt', ''),
219219
file_path=file_path,
@@ -248,7 +248,7 @@ def is_payload_for_ide(payload: dict, ide: str) -> bool:
248248
return True
249249

250250
@classmethod
251-
def from_payload(cls, payload: dict, tool: str = AIIDEType.CURSOR) -> 'AIHookPayload':
251+
def from_payload(cls, payload: dict, tool: str = AIIDEType.CURSOR.value) -> 'AIHookPayload':
252252
"""Create AIHookPayload from any tool's payload.
253253
254254
Args:

cycode/cli/apps/ai_guardrails/scan/response_builders.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def deny_prompt(self, user_message: str) -> dict:
117117
}
118118

119119

120-
def get_response_builder(ide: str = AIIDEType.CURSOR) -> IDEResponseBuilder:
120+
def get_response_builder(ide: str = AIIDEType.CURSOR.value) -> IDEResponseBuilder:
121121
"""Get the response builder for a specific IDE.
122122
123123
Args:
@@ -129,10 +129,7 @@ def get_response_builder(ide: str = AIIDEType.CURSOR) -> IDEResponseBuilder:
129129
Raises:
130130
ValueError: If the IDE is not supported
131131
"""
132-
# Normalize to AIIDEType if string passed
133-
if isinstance(ide, str):
134-
ide = ide.lower()
135-
builder = _RESPONSE_BUILDERS.get(ide)
132+
builder = _RESPONSE_BUILDERS.get(ide.lower())
136133
if not builder:
137134
raise ValueError(f'Unsupported IDE: {ide}. Supported IDEs: {list(_RESPONSE_BUILDERS.keys())}')
138135
return builder

cycode/cli/apps/ai_guardrails/scan/scan_command.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def scan_command(
6969
help='IDE that sent the payload (e.g., "cursor"). Defaults to cursor.',
7070
hidden=True,
7171
),
72-
] = AIIDEType.CURSOR,
72+
] = AIIDEType.CURSOR.value,
7373
) -> None:
7474
"""Scan content from AI IDE hooks for secrets.
7575

cycode/cli/apps/ai_guardrails/status_command.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def status_command(
2828
'--ide',
2929
help='IDE to check status for (e.g., "cursor", "claude-code", or "all" for all IDEs). Defaults to cursor.',
3030
),
31-
] = AIIDEType.CURSOR,
31+
] = AIIDEType.CURSOR.value,
3232
repo_path: Annotated[
3333
Optional[Path],
3434
typer.Option(

cycode/cli/apps/ai_guardrails/uninstall_command.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def uninstall_command(
3131
'--ide',
3232
help='IDE to uninstall hooks from (e.g., "cursor", "claude-code", "all"). Defaults to cursor.',
3333
),
34-
] = AIIDEType.CURSOR,
34+
] = AIIDEType.CURSOR.value,
3535
repo_path: Annotated[
3636
Optional[Path],
3737
typer.Option(

tests/cli/commands/ai_guardrails/scan/test_scan_command.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
import pytest
88
from pytest_mock import MockerFixture
9+
from typer.testing import CliRunner
910

11+
from cycode.cli.apps.ai_guardrails import app as ai_guardrails_app
1012
from cycode.cli.apps.ai_guardrails.scan.scan_command import scan_command
1113

1214

@@ -136,3 +138,32 @@ def test_claude_code_payload_with_claude_code_ide(
136138
mock_scan_command_deps['load_policy'].assert_called_once()
137139
mock_scan_command_deps['get_handler'].assert_called_once()
138140
mock_handler.assert_called_once()
141+
142+
143+
class TestDefaultIdeParameterViaCli:
144+
"""Tests that verify default IDE parameter works correctly via CLI invocation."""
145+
146+
def test_scan_command_default_ide_via_cli(self, mocker: MockerFixture) -> None:
147+
"""Test scan_command works with default --ide when invoked via CLI.
148+
149+
This test catches issues where Typer converts enum defaults to strings
150+
incorrectly (e.g., AIIDEType.CURSOR becomes 'AIIDEType.CURSOR' instead of 'cursor').
151+
"""
152+
mocker.patch('cycode.cli.apps.ai_guardrails.scan.scan_command._initialize_clients')
153+
mocker.patch(
154+
'cycode.cli.apps.ai_guardrails.scan.scan_command.load_policy',
155+
return_value={'fail_open': True},
156+
)
157+
mock_handler = MagicMock(return_value={'continue': True})
158+
mocker.patch(
159+
'cycode.cli.apps.ai_guardrails.scan.scan_command.get_handler_for_event',
160+
return_value=mock_handler,
161+
)
162+
163+
runner = CliRunner()
164+
payload = json.dumps({'hook_event_name': 'beforeSubmitPrompt', 'prompt': 'test'})
165+
166+
# Invoke via CLI without --ide flag to use default
167+
result = runner.invoke(ai_guardrails_app, ['scan'], input=payload)
168+
169+
assert result.exit_code == 0, f'Command failed: {result.output}'

0 commit comments

Comments
 (0)