cycode 3.15.3.dev7__py3-none-any.whl → 3.15.4.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,26 +1,22 @@
1
- """
2
- Scan command for AI guardrails.
1
+ """Scan command for AI guardrails IDE hooks.
3
2
 
4
- This command handles AI IDE hooks by reading JSON from stdin and outputting
5
- a JSON response to stdout. It scans prompts, file reads, and MCP tool calls
6
- for secrets before they are sent to AI models.
3
+ Reads a JSON payload from stdin, routes it through the IDE-specific parser and
4
+ the shared event handlers, then writes an IDE-specific JSON response to stdout.
7
5
 
8
- Supports multiple IDEs with different hook event types. The specific hook events
9
- supported depend on the IDE being used (e.g., Cursor supports beforeSubmitPrompt,
10
- beforeReadFile, beforeMCPExecution).
6
+ The handlers in ``handlers.py`` are agent-agnostic (they return
7
+ ``HookDecision``); ``IDE.build_hook_response`` is the per-IDE translation step.
11
8
  """
12
9
 
13
10
  import sys
14
- from typing import Annotated
11
+ from typing import Annotated, Optional, Union
15
12
 
16
13
  import click
17
14
  import typer
18
15
 
19
- from cycode.cli.apps.ai_guardrails.consts import AIIDEType
16
+ from cycode.cli.apps.ai_guardrails.ides import DEFAULT_IDE_NAME, get_ide
17
+ from cycode.cli.apps.ai_guardrails.ides.base import HookDecision
20
18
  from cycode.cli.apps.ai_guardrails.scan.handlers import get_handler_for_event
21
- from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
22
19
  from cycode.cli.apps.ai_guardrails.scan.policy import load_policy
23
- from cycode.cli.apps.ai_guardrails.scan.response_builders import get_response_builder
24
20
  from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType
25
21
  from cycode.cli.apps.ai_guardrails.scan.utils import output_json, safe_json_parse
26
22
  from cycode.cli.exceptions.custom_exceptions import HttpUnauthorizedError
@@ -31,7 +27,7 @@ logger = get_logger('AI Guardrails')
31
27
 
32
28
 
33
29
  def _get_auth_error_message(error: Exception) -> str:
34
- """Get user-friendly message for authentication errors."""
30
+ """User-friendly message for authentication errors."""
35
31
  if isinstance(error, click.ClickException):
36
32
  # Missing credentials
37
33
  return f'{error.message} Please run `cycode auth` to set up your credentials.'
@@ -47,6 +43,23 @@ def _get_auth_error_message(error: Exception) -> str:
47
43
  return 'Authentication failed. Please run `cycode auth` to set up your credentials.'
48
44
 
49
45
 
46
+ def _deny_for_event(
47
+ event_name: Optional[Union[str, AiHookEventType]],
48
+ user_message: str,
49
+ agent_message: Optional[str] = None,
50
+ ) -> HookDecision:
51
+ """Build a deny decision matched to ``event_name``'s response shape.
52
+
53
+ PROMPT events use the prompt-block shape (no agent_message). For anything
54
+ else — including unknown event names — fall back to FILE_READ since
55
+ FILE_READ and MCP_EXECUTION share the same response shape on both IDEs.
56
+ """
57
+ if event_name == AiHookEventType.PROMPT:
58
+ return HookDecision.deny(AiHookEventType.PROMPT, user_message)
59
+ target = event_name if isinstance(event_name, AiHookEventType) else AiHookEventType.FILE_READ
60
+ return HookDecision.deny(target, user_message, agent_message)
61
+
62
+
50
63
  def _initialize_clients(ctx: typer.Context) -> None:
51
64
  """Initialize API clients.
52
65
 
@@ -69,44 +82,36 @@ def scan_command(
69
82
  help='IDE that sent the payload (e.g., "cursor"). Defaults to cursor.',
70
83
  hidden=True,
71
84
  ),
72
- ] = AIIDEType.CURSOR.value,
85
+ ] = DEFAULT_IDE_NAME,
73
86
  ) -> None:
74
87
  """Scan content from AI IDE hooks for secrets.
75
88
 
76
- This command reads a JSON payload from stdin containing hook event data
77
- and outputs a JSON response to stdout indicating whether to allow or block the action.
78
-
79
- The hook event type is determined from the event field in the payload (field name
80
- varies by IDE). Each IDE may support different hook events for scanning prompts,
81
- file access, and tool executions.
82
-
83
- Example usage (from IDE hooks configuration):
84
- { "command": "cycode ai-guardrails scan" }
89
+ Reads a JSON payload from stdin and outputs a JSON response to stdout
90
+ indicating whether to allow or block the action.
85
91
  """
92
+ ide_integration = get_ide(ide)
93
+
86
94
  stdin_data = sys.stdin.read().strip()
87
95
  payload = safe_json_parse(stdin_data)
88
96
 
89
- tool = ide.lower()
90
- response_builder = get_response_builder(tool)
91
-
92
97
  if not payload:
93
98
  logger.debug('Empty or invalid JSON payload received')
94
- output_json(response_builder.allow_prompt())
99
+ output_json(ide_integration.build_hook_response(HookDecision.allow(AiHookEventType.PROMPT)))
95
100
  return
96
101
 
97
- # Check if the payload matches the expected IDE - prevents double-processing
98
- # when Cursor reads Claude Code hooks from ~/.claude/settings.json
99
- if not AIHookPayload.is_payload_for_ide(payload, tool):
102
+ # Prevent cross-IDE processing (e.g. Cursor reading Claude Code hooks
103
+ # from ~/.claude/settings.json).
104
+ if not ide_integration.matches_payload(payload):
100
105
  logger.debug(
101
106
  'Payload event does not match expected IDE, skipping',
102
- extra={'hook_event_name': payload.get('hook_event_name'), 'expected_ide': tool},
107
+ extra={'hook_event_name': payload.get('hook_event_name'), 'expected_ide': ide_integration.name},
103
108
  )
104
- output_json(response_builder.allow_prompt())
109
+ output_json(ide_integration.build_hook_response(HookDecision.allow(AiHookEventType.PROMPT)))
105
110
  return
106
111
 
107
- unified_payload = AIHookPayload.from_payload(payload, tool=tool)
112
+ unified_payload = ide_integration.parse_hook_payload(payload)
108
113
  event_name = unified_payload.event_name
109
- logger.debug('Processing AI guardrails hook', extra={'event_name': event_name, 'tool': tool})
114
+ logger.debug('Processing AI guardrails hook', extra={'event_name': event_name, 'ide': ide_integration.name})
110
115
 
111
116
  workspace_roots = payload.get('workspace_roots', ['.'])
112
117
  policy = load_policy(workspace_roots[0])
@@ -117,26 +122,33 @@ def scan_command(
117
122
  handler = get_handler_for_event(event_name)
118
123
  if handler is None:
119
124
  logger.debug('Unknown hook event, allowing by default', extra={'event_name': event_name})
120
- output_json(response_builder.allow_prompt())
125
+ output_json(ide_integration.build_hook_response(HookDecision.allow(AiHookEventType.PROMPT)))
121
126
  return
122
127
 
123
- response = handler(ctx, unified_payload, policy)
124
- logger.debug('Hook handler completed', extra={'event_name': event_name, 'response': response})
125
- output_json(response)
128
+ decision = handler(ctx, unified_payload, policy)
129
+ logger.debug('Hook handler completed', extra={'event_name': event_name, 'action': decision.action.value})
130
+ output_json(ide_integration.build_hook_response(decision))
126
131
 
127
132
  except (click.ClickException, HttpUnauthorizedError) as e:
128
- error_message = _get_auth_error_message(e)
129
- if event_name == AiHookEventType.PROMPT:
130
- output_json(response_builder.deny_prompt(error_message))
131
- return
132
- output_json(response_builder.deny_permission(error_message, 'Authentication required'))
133
+ output_json(
134
+ ide_integration.build_hook_response(
135
+ _deny_for_event(event_name, _get_auth_error_message(e), 'Authentication required')
136
+ )
137
+ )
133
138
 
134
139
  except Exception as e:
135
140
  logger.error('Hook handler failed', exc_info=e)
136
141
  if policy.get('fail_open', True):
137
- output_json(response_builder.allow_prompt())
138
- return
139
- if event_name == AiHookEventType.PROMPT:
140
- output_json(response_builder.deny_prompt('Cycode guardrails error - blocking due to fail-closed policy'))
142
+ output_json(ide_integration.build_hook_response(HookDecision.allow(AiHookEventType.PROMPT)))
141
143
  return
142
- output_json(response_builder.deny_permission('Cycode guardrails error', 'Blocking due to fail-closed policy'))
144
+ output_json(
145
+ ide_integration.build_hook_response(
146
+ _deny_for_event(
147
+ event_name,
148
+ 'Cycode guardrails error - blocking due to fail-closed policy'
149
+ if event_name == AiHookEventType.PROMPT
150
+ else 'Cycode guardrails error',
151
+ 'Blocking due to fail-closed policy',
152
+ )
153
+ )
154
+ )
@@ -1,4 +1,9 @@
1
- """Type definitions for AI guardrails."""
1
+ """Canonical event types and outcome enums for AI guardrails.
2
+
3
+ Per-IDE event-name mappings live on the IDE class (in
4
+ `cycode/cli/apps/ai_guardrails/ides/`); only the IDE-agnostic enums are kept
5
+ here.
6
+ """
2
7
 
3
8
  import sys
4
9
 
@@ -13,36 +18,13 @@ else:
13
18
 
14
19
 
15
20
  class AiHookEventType(StrEnum):
16
- """Canonical event types for AI guardrails.
17
-
18
- These are IDE-agnostic event types. Each IDE's specific event names
19
- are mapped to these canonical types using the mapping dictionaries below.
20
- """
21
+ """Canonical, IDE-agnostic hook event types."""
21
22
 
22
23
  PROMPT = 'Prompt'
23
24
  FILE_READ = 'FileRead'
24
25
  MCP_EXECUTION = 'McpExecution'
25
26
 
26
27
 
27
- # IDE-specific event name mappings to canonical types
28
- CURSOR_EVENT_MAPPING = {
29
- 'beforeSubmitPrompt': AiHookEventType.PROMPT,
30
- 'beforeReadFile': AiHookEventType.FILE_READ,
31
- 'beforeMCPExecution': AiHookEventType.MCP_EXECUTION,
32
- }
33
-
34
- # Claude Code event mapping - note that PreToolUse requires tool_name inspection
35
- # to determine the actual event type (file read vs MCP execution)
36
- CLAUDE_CODE_EVENT_MAPPING = {
37
- 'UserPromptSubmit': AiHookEventType.PROMPT,
38
- 'PreToolUse': None, # Requires tool_name inspection to determine actual type
39
- }
40
-
41
- # Set of known event names per IDE (for IDE detection)
42
- CURSOR_EVENT_NAMES = set(CURSOR_EVENT_MAPPING.keys())
43
- CLAUDE_CODE_EVENT_NAMES = set(CLAUDE_CODE_EVENT_MAPPING.keys())
44
-
45
-
46
28
  class AIHookOutcome(StrEnum):
47
29
  """Outcome of an AI hook event evaluation."""
48
30
 
@@ -52,11 +34,7 @@ class AIHookOutcome(StrEnum):
52
34
 
53
35
 
54
36
  class BlockReason(StrEnum):
55
- """Reason why an AI hook event was blocked.
56
-
57
- These are categorical reasons sent to the backend for tracking/analytics,
58
- separate from the detailed user-facing messages.
59
- """
37
+ """Categorical reason for blocking (sent to backend for tracking)."""
60
38
 
61
39
  SECRETS_IN_PROMPT = 'secrets_in_prompt'
62
40
  SECRETS_IN_FILE = 'secrets_in_file'
@@ -1,18 +1,12 @@
1
+ """Handle AI guardrails session start: auth, conversation creation, session context."""
2
+
1
3
  import sys
2
4
  from typing import TYPE_CHECKING, Annotated, Optional
3
5
 
4
6
  import typer
5
7
 
6
- from cycode.cli.apps.ai_guardrails.consts import AIIDEType
7
- from cycode.cli.apps.ai_guardrails.scan.claude_config import (
8
- get_mcp_servers,
9
- get_user_email,
10
- load_claude_config,
11
- load_claude_settings,
12
- resolve_plugins,
13
- )
14
- from cycode.cli.apps.ai_guardrails.scan.cursor_config import load_cursor_config
15
- from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload, extract_from_claude_transcript
8
+ from cycode.cli.apps.ai_guardrails.ides import DEFAULT_IDE_NAME, get_ide
9
+ from cycode.cli.apps.ai_guardrails.ides.base import IDE
16
10
  from cycode.cli.apps.ai_guardrails.scan.utils import safe_json_parse
17
11
  from cycode.cli.apps.auth.auth_common import get_authorization_info
18
12
  from cycode.cli.apps.auth.auth_manager import AuthManager
@@ -26,69 +20,10 @@ if TYPE_CHECKING:
26
20
  logger = get_logger('AI Guardrails')
27
21
 
28
22
 
29
- def _build_session_payload(payload: dict, ide: str) -> AIHookPayload:
30
- """Build an AIHookPayload from a session-start stdin payload."""
31
- if ide == AIIDEType.CLAUDE_CODE:
32
- claude_config = load_claude_config()
33
- ide_user_email = get_user_email(claude_config) if claude_config else None
34
- ide_version, _, _ = extract_from_claude_transcript(payload.get('transcript_path'))
35
-
36
- return AIHookPayload(
37
- conversation_id=payload.get('session_id'),
38
- ide_user_email=ide_user_email,
39
- model=payload.get('model'),
40
- ide_provider=AIIDEType.CLAUDE_CODE.value,
41
- ide_version=ide_version,
42
- source=payload.get('source'),
43
- )
44
-
45
- # Cursor
46
- return AIHookPayload(
47
- conversation_id=payload.get('conversation_id'),
48
- ide_user_email=payload.get('user_email'),
49
- model=payload.get('model'),
50
- ide_provider=AIIDEType.CURSOR.value,
51
- ide_version=payload.get('cursor_version'),
52
- )
53
-
54
-
55
- def _get_claude_code_session_context() -> tuple[dict, dict]:
56
- """Return (mcp_servers, enabled_plugins) for Claude Code.
57
-
58
- Merges MCP servers from ~/.claude.json (user-configured) with those contributed
59
- by enabled plugins. Plugin metadata (name, version, description) is included in
60
- the enabled_plugins dict when resolvable.
61
- """
62
- config = load_claude_config()
63
- mcp_servers = dict(get_mcp_servers(config) or {}) if config else {}
64
-
65
- settings = load_claude_settings()
66
- if settings:
67
- plugin_mcp, enriched_plugins = resolve_plugins(settings)
68
- mcp_servers.update(plugin_mcp)
69
- else:
70
- enriched_plugins = {}
71
-
72
- return mcp_servers, enriched_plugins
73
-
74
-
75
- def _get_cursor_session_context() -> tuple[dict, dict]:
76
- """Return (mcp_servers, enabled_plugins) for Cursor. Cursor has no plugin system."""
77
- config = load_cursor_config()
78
- mcp_servers = dict(get_mcp_servers(config) or {}) if config else {}
79
- return mcp_servers, {}
80
-
81
-
82
- def _report_session_context(ai_client: 'AISecurityManagerClient', ide: str, user_email: Optional[str]) -> None:
23
+ def _report_session_context(ai_client: 'AISecurityManagerClient', ide: IDE, user_email: Optional[str]) -> None:
83
24
  """Report IDE session context to the AI security manager. Never raises."""
84
25
  try:
85
- if ide == AIIDEType.CLAUDE_CODE:
86
- mcp_servers, enabled_plugins = _get_claude_code_session_context()
87
- elif ide == AIIDEType.CURSOR:
88
- mcp_servers, enabled_plugins = _get_cursor_session_context()
89
- else:
90
- return
91
-
26
+ mcp_servers, enabled_plugins = ide.get_session_context()
92
27
  if not mcp_servers and not enabled_plugins:
93
28
  return
94
29
  ai_client.report_session_context(
@@ -109,16 +44,17 @@ def session_start_command(
109
44
  help='IDE that triggered the session start.',
110
45
  hidden=True,
111
46
  ),
112
- ] = AIIDEType.CURSOR.value,
47
+ ] = DEFAULT_IDE_NAME,
113
48
  ) -> None:
114
49
  """Handle session start: ensure auth, create conversation, report session context."""
50
+ ide_integration = get_ide(ide)
51
+
115
52
  # Step 1: Ensure authentication
116
53
  auth_info = get_authorization_info(ctx)
117
54
  if auth_info is None:
118
55
  logger.debug('Not authenticated, starting authentication')
119
56
  try:
120
- auth_manager = AuthManager()
121
- auth_manager.authenticate()
57
+ AuthManager().authenticate()
122
58
  except Exception as err:
123
59
  handle_auth_exception(ctx, err)
124
60
  return
@@ -136,8 +72,8 @@ def session_start_command(
136
72
  logger.debug('Empty or invalid stdin payload, skipping session initialization')
137
73
  return
138
74
 
139
- # Step 3: Build session payload and initialize API client
140
- session_payload = _build_session_payload(payload, ide)
75
+ # Step 3: Build session payload + initialize API client
76
+ session_payload = ide_integration.build_session_payload(payload)
141
77
 
142
78
  try:
143
79
  ai_client = get_ai_security_manager_client(ctx)
@@ -151,5 +87,5 @@ def session_start_command(
151
87
  except Exception as e:
152
88
  logger.debug('Failed to create conversation during session start', exc_info=e)
153
89
 
154
- # Step 5: Report session context (MCP servers)
155
- _report_session_context(ai_client, ide, session_payload.ide_user_email)
90
+ # Step 5: Report session context (MCP servers, enabled plugins)
91
+ _report_session_context(ai_client, ide_integration, session_payload.ide_user_email)
@@ -7,9 +7,9 @@ from typing import Annotated, Optional
7
7
  import typer
8
8
  from rich.table import Table
9
9
 
10
- from cycode.cli.apps.ai_guardrails.command_utils import console, validate_and_parse_ide, validate_scope
11
- from cycode.cli.apps.ai_guardrails.consts import IDE_CONFIGS, AIIDEType
10
+ from cycode.cli.apps.ai_guardrails.command_utils import console, validate_scope
12
11
  from cycode.cli.apps.ai_guardrails.hooks_manager import get_hooks_status
12
+ from cycode.cli.apps.ai_guardrails.ides import DEFAULT_IDE_NAME, IDES, resolve_ides
13
13
 
14
14
 
15
15
  def status_command(
@@ -26,9 +26,9 @@ def status_command(
26
26
  str,
27
27
  typer.Option(
28
28
  '--ide',
29
- help='IDE to check status for (e.g., "cursor", "claude-code", or "all" for all IDEs). Defaults to cursor.',
29
+ help=f'IDE to check status for ({", ".join(IDES)}, or "all").',
30
30
  ),
31
- ] = AIIDEType.CURSOR.value,
31
+ ] = DEFAULT_IDE_NAME,
32
32
  repo_path: Annotated[
33
33
  Optional[Path],
34
34
  typer.Option(
@@ -43,32 +43,30 @@ def status_command(
43
43
  ) -> None:
44
44
  """Show AI guardrails hook installation status.
45
45
 
46
- Displays the current status of Cycode AI guardrails hooks for the specified IDE.
47
-
48
46
  Examples:
49
47
  cycode ai-guardrails status # Show both user and repo status
50
48
  cycode ai-guardrails status --scope user # Show only user-level status
51
49
  cycode ai-guardrails status --scope repo # Show only repo-level status
52
- cycode ai-guardrails status --ide cursor # Check status for Cursor IDE
53
- cycode ai-guardrails status --ide all # Check status for all supported IDEs
50
+ cycode ai-guardrails status --ide claude-code
51
+ cycode ai-guardrails status --ide all # Check every supported IDE
54
52
  """
55
- # Validate inputs (status allows 'all' scope)
56
53
  validate_scope(scope, allowed_scopes=('user', 'repo', 'all'))
57
54
  if repo_path is None:
58
55
  repo_path = Path(os.getcwd())
59
- ide_type = validate_and_parse_ide(ide)
60
-
61
- ides_to_check: list[AIIDEType] = list(AIIDEType) if ide_type is None else [ide_type]
56
+ ides_to_check = resolve_ides(ide)
62
57
 
63
58
  scopes_to_check = ['user', 'repo'] if scope == 'all' else [scope]
64
59
 
65
60
  for current_ide in ides_to_check:
66
- ide_name = IDE_CONFIGS[current_ide].name
67
61
  console.print()
68
- console.print(f'[bold cyan]═══ {ide_name} ═══[/]')
62
+ console.print(f'[bold cyan]═══ {current_ide.display_name} ═══[/]')
69
63
 
70
64
  for check_scope in scopes_to_check:
71
- status = get_hooks_status(check_scope, repo_path if check_scope == 'repo' else None, ide=current_ide)
65
+ status = get_hooks_status(
66
+ current_ide,
67
+ check_scope,
68
+ repo_path if check_scope == 'repo' else None,
69
+ )
72
70
 
73
71
  console.print()
74
72
  console.print(f'[bold]{check_scope.upper()} SCOPE[/]')
@@ -83,7 +81,6 @@ def status_command(
83
81
  else:
84
82
  console.print('[yellow]○ Cycode AI guardrails: NOT INSTALLED[/]')
85
83
 
86
- # Show hook details
87
84
  table = Table(show_header=True, header_style='bold')
88
85
  table.add_column('Hook Event')
89
86
  table.add_column('Cycode Enabled')
@@ -5,14 +5,9 @@ from typing import Annotated, Optional
5
5
 
6
6
  import typer
7
7
 
8
- from cycode.cli.apps.ai_guardrails.command_utils import (
9
- console,
10
- resolve_repo_path,
11
- validate_and_parse_ide,
12
- validate_scope,
13
- )
14
- from cycode.cli.apps.ai_guardrails.consts import IDE_CONFIGS, AIIDEType
8
+ from cycode.cli.apps.ai_guardrails.command_utils import console, resolve_repo_path, validate_scope
15
9
  from cycode.cli.apps.ai_guardrails.hooks_manager import uninstall_hooks
10
+ from cycode.cli.apps.ai_guardrails.ides import DEFAULT_IDE_NAME, IDES, resolve_ides
16
11
 
17
12
 
18
13
  def uninstall_command(
@@ -29,9 +24,9 @@ def uninstall_command(
29
24
  str,
30
25
  typer.Option(
31
26
  '--ide',
32
- help='IDE to uninstall hooks from (e.g., "cursor", "claude-code", "all"). Defaults to cursor.',
27
+ help=f'IDE to uninstall hooks from ({", ".join(IDES)}, or "all").',
33
28
  ),
34
- ] = AIIDEType.CURSOR.value,
29
+ ] = DEFAULT_IDE_NAME,
35
30
  repo_path: Annotated[
36
31
  Optional[Path],
37
32
  typer.Option(
@@ -46,32 +41,27 @@ def uninstall_command(
46
41
  ) -> None:
47
42
  """Remove AI guardrails hooks from supported IDEs.
48
43
 
49
- This command removes Cycode hooks from the IDE's hooks configuration.
50
- Other hooks (if any) will be preserved.
44
+ Removes Cycode hooks from the IDE's hooks configuration. Other hooks
45
+ (if any) are preserved.
51
46
 
52
47
  Examples:
53
48
  cycode ai-guardrails uninstall # Remove user-level hooks
54
49
  cycode ai-guardrails uninstall --scope repo # Remove repo-level hooks
55
- cycode ai-guardrails uninstall --ide cursor # Uninstall from Cursor IDE
56
- cycode ai-guardrails uninstall --ide all # Uninstall from all supported IDEs
50
+ cycode ai-guardrails uninstall --ide claude-code # Uninstall from a specific IDE
51
+ cycode ai-guardrails uninstall --ide all # Uninstall from every supported IDE
57
52
  """
58
- # Validate inputs
59
53
  validate_scope(scope)
60
54
  repo_path = resolve_repo_path(scope, repo_path)
61
- ide_type = validate_and_parse_ide(ide)
62
-
63
- ides_to_uninstall: list[AIIDEType] = list(AIIDEType) if ide_type is None else [ide_type]
55
+ ides_to_uninstall = resolve_ides(ide)
64
56
 
65
57
  results: list[tuple[str, bool, str]] = []
66
58
  for current_ide in ides_to_uninstall:
67
- ide_name = IDE_CONFIGS[current_ide].name
68
- success, message = uninstall_hooks(scope, repo_path, ide=current_ide)
69
- results.append((ide_name, success, message))
59
+ success, message = uninstall_hooks(current_ide, scope, repo_path)
60
+ results.append((current_ide.display_name, success, message))
70
61
 
71
- # Report results for each IDE
72
62
  any_success = False
73
63
  all_success = True
74
- for _ide_name, success, message in results:
64
+ for _name, success, message in results:
75
65
  if success:
76
66
  console.print(f'[green]✓[/] {message}')
77
67
  any_success = True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cycode
3
- Version: 3.15.3.dev7
3
+ Version: 3.15.4.dev1
4
4
  Summary: Boost security in your dev lifecycle via SAST, SCA, Secrets & IaC scanning.
5
5
  License-Expression: MIT
6
6
  License-File: LICENCE
@@ -62,6 +62,7 @@ This guide walks you through both installation and usage.
62
62
  2. [Available Options](#available-options)
63
63
  3. [MCP Tools](#mcp-tools)
64
64
  4. [Usage Examples](#usage-examples)
65
+ 5. [Advanced Configuration](#advanced-configuration)
65
66
  5. [Platform Command](#platform-command-beta)
66
67
  1. [Discovering Commands](#discovering-commands)
67
68
  2. [Examples](#platform-examples)
@@ -600,6 +601,38 @@ cycode mcp -t streamable-http -H 127.0.0.2 -p 9000 &
600
601
  }
601
602
  ```
602
603
 
604
+ ### Advanced Configuration
605
+ ##### Custom Certificates and Timeouts (Proxy Environments)
606
+
607
+ If your organization uses a corporate proxy or a custom CA bundle for HTTPS inspection, you need to tell Cycode CLI (and the underlying Python TLS stack) where to find the trusted certificate bundle. You can also increase the MCP tool call timeout if scans are being cut short.
608
+
609
+ | Environment Variable | Description |
610
+ |----------------------|-------------|
611
+ | `REQUESTS_CA_BUNDLE` | Path to a custom CA bundle file (`.pem` or `.crt`). Used by the `requests` library for all HTTPS calls made by Cycode CLI. |
612
+ | `SSL_CERT_FILE` | Path to a custom CA bundle file. Used by Python's low-level `ssl` module. Set this alongside `REQUESTS_CA_BUNDLE` for full coverage. |
613
+ | `MCP_TOOL_TIMEOUT` | Timeout (in seconds) that MCP clients such as Claude and GitHub Copilot wait for a tool call to complete. Increase this if long-running scans are being cut off before they finish. |
614
+
615
+ > [!TIP]
616
+ > Set both `REQUESTS_CA_BUNDLE` and `SSL_CERT_FILE` to the same CA bundle path. `REQUESTS_CA_BUNDLE` covers the HTTP layer; `SSL_CERT_FILE` covers the lower-level TLS layer. Using only one may still cause certificate errors in some environments.
617
+
618
+ Example `mcp.json` configuration with custom certificates and a longer timeout:
619
+
620
+ ```json
621
+ {
622
+ "mcpServers": {
623
+ "cycode": {
624
+ "command": "cycode",
625
+ "args": ["mcp"],
626
+ "env": {
627
+ "REQUESTS_CA_BUNDLE": "/path/to/your/corporate-ca-bundle.pem",
628
+ "SSL_CERT_FILE": "/path/to/your/corporate-ca-bundle.pem",
629
+ "MCP_TOOL_TIMEOUT": "1800"
630
+ }
631
+ }
632
+ }
633
+ }
634
+ ```
635
+
603
636
  > [!NOTE]
604
637
  > The MCP server requires proper Cycode CLI authentication to function. Make sure you have authenticated using `cycode auth` or configured your credentials before starting the MCP server.
605
638
 
@@ -649,6 +682,8 @@ This information can be helpful when:
649
682
  - Identifying authentication problems
650
683
  - Debugging transport-specific issues
651
684
 
685
+ ### MCP Configuration
686
+
652
687
 
653
688
  # Platform Command \[BETA\]
654
689
 
@@ -1,28 +1,29 @@
1
- cycode/__init__.py,sha256=XvbLa7dFUERdk8IUnLa6Bb3K2FbGOql-Ipx1O_kb6Ng,115
1
+ cycode/__init__.py,sha256=UPgVpILY2bWvXK6Bul8uIehLpBu3njNwqn83H0Jz-wo,115
2
2
  cycode/__main__.py,sha256=Z3bD5yrA7yPvAChcADQrqCaZd0ChGI1gdiwALwbWJ6U,104
3
3
  cycode/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  cycode/cli/app.py,sha256=7ReEcVkRX9IaQ2I7jAj7Sl9smbtvxiuK8-9bitMEQik,7491
5
5
  cycode/cli/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  cycode/cli/apps/activation_manager.py,sha256=Hz9PDJFB-ZmYi4HSG8iYC-fR8j5v25VuUU-l95Otsdk,1678
7
7
  cycode/cli/apps/ai_guardrails/__init__.py,sha256=NsqB1Ca83BIjJMcDSt6suec6Ed0iNnacC0gBqkuuTtI,1367
8
- cycode/cli/apps/ai_guardrails/command_utils.py,sha256=itWoARiiqC-kCJuppBxBwKDjCSnci2m0EG95GQPy3r4,1924
9
- cycode/cli/apps/ai_guardrails/consts.py,sha256=hJ_P6OKtylrhcOsiLmsyRaL0kQ8YsDgmREP_xY8JTkk,4449
10
- cycode/cli/apps/ai_guardrails/hooks_manager.py,sha256=37IcEMCK60pQ8lnuy8GThlq9oeNOfETVp_xYGeJ9EpM,9428
11
- cycode/cli/apps/ai_guardrails/install_command.py,sha256=qlklts1Uj6j3urK6jwAWJY-L_DgVaZWuk7vZcpoKPAQ,4571
8
+ cycode/cli/apps/ai_guardrails/command_utils.py,sha256=NVwd0-2RGRKIqhsQ-4LNDR1D0gVm_o7n-z5LxG2bqAo,800
9
+ cycode/cli/apps/ai_guardrails/consts.py,sha256=Js2QtSNYG9Kt0eo3vepRd5TFciCeJHEC9NN18zqKvlE,620
10
+ cycode/cli/apps/ai_guardrails/hooks_manager.py,sha256=dXrVMD_4CcN5aZNZetgtajJab0g4lVGW5ktoS7ALxzc,6864
11
+ cycode/cli/apps/ai_guardrails/ides/__init__.py,sha256=1HgTyTmsX-JPGiYk1at7jbzlay6ASkKfA5RXo7K1_a8,1621
12
+ cycode/cli/apps/ai_guardrails/ides/base.py,sha256=_KzOlD_tTU0HcZ-NaXc6NZ0Tn0ngLxqovQtY_Pcj5H4,5752
13
+ cycode/cli/apps/ai_guardrails/ides/claude_code.py,sha256=YqgxV_kQ39LUlAA9fQbgQmWC7ALC1xMMaEVdsObZtEQ,14197
14
+ cycode/cli/apps/ai_guardrails/ides/cursor.py,sha256=MnzhEgYYukTx7XVdNu-u5yO_PzOrZNteTIIEf0mymYc,5237
15
+ cycode/cli/apps/ai_guardrails/install_command.py,sha256=vGZSIvHHVMS9_zhV_6lEhxqtmr5H6uykSq4AS5nxQYw,4278
12
16
  cycode/cli/apps/ai_guardrails/scan/__init__.py,sha256=qJc82XiQGiAuc1sYY8Ij_A-qXpxgLPuayQq8xWlouMA,48
13
- cycode/cli/apps/ai_guardrails/scan/claude_config.py,sha256=2hVuPHfT-9_kgf5yCNgN522IcporEZvJEyYTLaaae2c,5195
14
17
  cycode/cli/apps/ai_guardrails/scan/consts.py,sha256=drAslw6vW3kxmbCs2qPCUbUPR7PJouT2lsXtu5sD-lQ,1094
15
- cycode/cli/apps/ai_guardrails/scan/cursor_config.py,sha256=D4bQsTu6MFzJFhygk0QCyopy5gJMkJm0oRrWNt2PC0g,1087
16
- cycode/cli/apps/ai_guardrails/scan/handlers.py,sha256=7zxGQrePRJcs6kkpKnbYq59wM2ADKz70Ve_ZB5ZNRQE,15573
17
- cycode/cli/apps/ai_guardrails/scan/payload.py,sha256=WGvniNa0PuqgGoeNdpXGAYK6aJqOiaB38xNUYaL2CWk,10458
18
+ cycode/cli/apps/ai_guardrails/scan/handlers.py,sha256=7hnawiQhH8rNKQ0DrSyzcy41C9YYeW7LYOJA1QUpkOM,14218
19
+ cycode/cli/apps/ai_guardrails/scan/payload.py,sha256=pvT3UUqNMvdK3EVzzPjy4JMlOrF-WgxZ3fHN2AtN5eA,1126
18
20
  cycode/cli/apps/ai_guardrails/scan/policy.py,sha256=39s8hnxgjny1l6XAO59wsRcAlpW-LG00GUnO0PfqvuY,2566
19
- cycode/cli/apps/ai_guardrails/scan/response_builders.py,sha256=tVFJCnGdqSmyileg-idypOihygct7F6T4KHXYlX8y_c,4653
20
- cycode/cli/apps/ai_guardrails/scan/scan_command.py,sha256=_2fa6vz8ZmJlvCYrYNoWfX9fWrrpzcNCwL1UD-JxqLM,5618
21
- cycode/cli/apps/ai_guardrails/scan/types.py,sha256=H25MKJhAXmp7Mz1YeCIRmAY1Zg5GSpgBq8G1TEI9PFk,1868
21
+ cycode/cli/apps/ai_guardrails/scan/scan_command.py,sha256=-Gl7cHELF1wlwLGno6ZVukFtK6NI6Z3-dTpIOsz8ors,6079
22
+ cycode/cli/apps/ai_guardrails/scan/types.py,sha256=lDttkYFBfOkdMEEaRbq1IT2QTK0R-7Ht3T8vZdyB3b4,1038
22
23
  cycode/cli/apps/ai_guardrails/scan/utils.py,sha256=KVfX-NrcM-QW4quLtoNqfmz4GF0FlDs-TkqUOu1hAWM,2057
23
- cycode/cli/apps/ai_guardrails/session_start_command.py,sha256=fmRQLFFgE0IN9ATVA6dTvkfu-ZF40JRLVo2eAy7pfnk,5618
24
- cycode/cli/apps/ai_guardrails/status_command.py,sha256=UerHtjIGi6sY4RXGR06Es6jQFQAEWTx2Dvhk784WQIM,3539
25
- cycode/cli/apps/ai_guardrails/uninstall_command.py,sha256=0qhXNC4PQPqrtt5JmexcM4W6i-VyvObB3DQT_DINM1Q,2969
24
+ cycode/cli/apps/ai_guardrails/session_start_command.py,sha256=z-yClXkV-SAxh5E8zKGIyo_qbhkpujoTgnx-lUDGS0I,3279
25
+ cycode/cli/apps/ai_guardrails/status_command.py,sha256=Uqss68TEPCYPXpLix6Bh-4J3g-khxWsAqlIGYH5x4bQ,3203
26
+ cycode/cli/apps/ai_guardrails/uninstall_command.py,sha256=dOmePfZmlHAPy2zEJM1yMtSuDqvzDwtqgmLKYK-T9PI,2698
26
27
  cycode/cli/apps/ai_remediation/__init__.py,sha256=8vYthY9RQeJqEni3AIF5sryz8n-XJQ6VNqG4aEFBAdY,553
27
28
  cycode/cli/apps/ai_remediation/ai_remediation_command.py,sha256=u1EdebaKCEmzv9fXmnIN0xDSLcCmGyjueYKvYfLOj_8,1549
28
29
  cycode/cli/apps/ai_remediation/apply_fix.py,sha256=9zgqiqF9HBQXi7Oz9ZIiANIAuKAMTji1PlNncCEOf5Q,817
@@ -204,8 +205,8 @@ cycode/cyclient/report_client.py,sha256=Scq30NeJPzgXv0hPLO1U05AdE9i_2iu6cIrSKpEJ
204
205
  cycode/cyclient/scan_client.py,sha256=6TK5FQkfrvV7PHqRnUzEn1PBNd2oPYVamvIixcUfe3c,16755
205
206
  cycode/cyclient/scan_config_base.py,sha256=mXsPZGYCtp85rv5GIige40yQZXuRcEKUW-VQJ0vgFzk,1201
206
207
  cycode/logger.py,sha256=EfZGRK6VC5rE_LAjIcRrHFiQCueylCDXoG6bvGkrIME,2111
207
- cycode-3.15.3.dev7.dist-info/METADATA,sha256=d0aIuuYsyssqRrtQgXFQLzOS7y2gZe2zmKtnZabff_M,87415
208
- cycode-3.15.3.dev7.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
209
- cycode-3.15.3.dev7.dist-info/entry_points.txt,sha256=iDcVJM8ByLElVgvBgtYxDjw1kT7O8Mo0LcWZIT5L3Ig,45
210
- cycode-3.15.3.dev7.dist-info/licenses/LICENCE,sha256=2Wx4N6mD_4xB7-E3hPkZ3MPhpJy__k_I8MaCSO-PDRo,1068
211
- cycode-3.15.3.dev7.dist-info/RECORD,,
208
+ cycode-3.15.4.dev1.dist-info/METADATA,sha256=zo-M7zLCUNGxECnRB06G7NpfNmynUAdahbN-tNa_0dI,89102
209
+ cycode-3.15.4.dev1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
210
+ cycode-3.15.4.dev1.dist-info/entry_points.txt,sha256=iDcVJM8ByLElVgvBgtYxDjw1kT7O8Mo0LcWZIT5L3Ig,45
211
+ cycode-3.15.4.dev1.dist-info/licenses/LICENCE,sha256=2Wx4N6mD_4xB7-E3hPkZ3MPhpJy__k_I8MaCSO-PDRo,1068
212
+ cycode-3.15.4.dev1.dist-info/RECORD,,