cycode 3.25.1.dev6__py3-none-any.whl → 3.25.2.dev2__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.
Files changed (125) hide show
  1. cycode/__init__.py +1 -1
  2. cycode/cli/app.py +9 -10
  3. cycode/cli/apps/activation_manager.py +6 -6
  4. cycode/cli/apps/ai_guardrails/command_utils.py +1 -2
  5. cycode/cli/apps/ai_guardrails/hooks_manager.py +6 -7
  6. cycode/cli/apps/ai_guardrails/ides/__init__.py +8 -9
  7. cycode/cli/apps/ai_guardrails/ides/_plugin_utils.py +11 -10
  8. cycode/cli/apps/ai_guardrails/ides/_skill_utils.py +1 -2
  9. cycode/cli/apps/ai_guardrails/ides/base.py +19 -21
  10. cycode/cli/apps/ai_guardrails/ides/claude_code.py +54 -16
  11. cycode/cli/apps/ai_guardrails/ides/codex.py +54 -20
  12. cycode/cli/apps/ai_guardrails/ides/copilot.py +23 -25
  13. cycode/cli/apps/ai_guardrails/ides/cursor.py +48 -10
  14. cycode/cli/apps/ai_guardrails/install_command.py +3 -3
  15. cycode/cli/apps/ai_guardrails/scan/guardrail_config.py +44 -22
  16. cycode/cli/apps/ai_guardrails/scan/handlers.py +79 -14
  17. cycode/cli/apps/ai_guardrails/scan/mcp_server_status.py +103 -0
  18. cycode/cli/apps/ai_guardrails/scan/payload.py +13 -14
  19. cycode/cli/apps/ai_guardrails/scan/policy.py +3 -3
  20. cycode/cli/apps/ai_guardrails/scan/scan_command.py +4 -4
  21. cycode/cli/apps/ai_guardrails/scan/types.py +1 -0
  22. cycode/cli/apps/ai_guardrails/session_start_command.py +46 -21
  23. cycode/cli/apps/ai_guardrails/status_command.py +2 -2
  24. cycode/cli/apps/ai_guardrails/uninstall_command.py +2 -2
  25. cycode/cli/apps/api/__init__.py +3 -3
  26. cycode/cli/apps/api/api_command.py +6 -6
  27. cycode/cli/apps/api/openapi_spec.py +4 -5
  28. cycode/cli/apps/auth/auth_common.py +4 -4
  29. cycode/cli/apps/configure/configure_command.py +2 -4
  30. cycode/cli/apps/configure/messages.py +2 -4
  31. cycode/cli/apps/configure/prompts.py +5 -7
  32. cycode/cli/apps/ignore/ignore_command.py +7 -7
  33. cycode/cli/apps/mcp/mcp_command.py +11 -11
  34. cycode/cli/apps/report/sbom/common.py +5 -5
  35. cycode/cli/apps/report/sbom/sbom_command.py +2 -2
  36. cycode/cli/apps/report/sbom/sbom_report_file.py +1 -2
  37. cycode/cli/apps/report_import/sbom/sbom_command.py +3 -3
  38. cycode/cli/apps/sca_options.py +3 -3
  39. cycode/cli/apps/scan/aggregation_report.py +4 -4
  40. cycode/cli/apps/scan/code_scanner.py +8 -7
  41. cycode/cli/apps/scan/commit_range_scanner.py +10 -10
  42. cycode/cli/apps/scan/detection_excluder.py +2 -4
  43. cycode/cli/apps/scan/pre_commit/pre_commit_command.py +3 -3
  44. cycode/cli/apps/scan/pre_push/pre_push_command.py +3 -3
  45. cycode/cli/apps/scan/pre_receive/pre_receive_command.py +2 -2
  46. cycode/cli/apps/scan/remote_url_resolver.py +6 -8
  47. cycode/cli/apps/scan/repository/repository_command.py +2 -2
  48. cycode/cli/apps/scan/scan_command.py +2 -2
  49. cycode/cli/apps/scan/scan_parameters.py +1 -3
  50. cycode/cli/apps/scan/scan_result.py +5 -5
  51. cycode/cli/console.py +2 -2
  52. cycode/cli/exceptions/custom_exceptions.py +1 -1
  53. cycode/cli/exceptions/handle_errors.py +1 -3
  54. cycode/cli/exceptions/handle_scan_errors.py +1 -2
  55. cycode/cli/files_collector/commit_range_documents.py +14 -14
  56. cycode/cli/files_collector/models/in_memory_zip.py +3 -3
  57. cycode/cli/files_collector/repository_documents.py +3 -3
  58. cycode/cli/files_collector/sca/base_restore_dependencies.py +7 -8
  59. cycode/cli/files_collector/sca/go/restore_go_dependencies.py +1 -2
  60. cycode/cli/files_collector/sca/maven/restore_gradle_dependencies.py +2 -3
  61. cycode/cli/files_collector/sca/maven/restore_maven_dependencies.py +3 -4
  62. cycode/cli/files_collector/sca/npm/restore_bun_dependencies.py +3 -4
  63. cycode/cli/files_collector/sca/npm/restore_deno_dependencies.py +1 -2
  64. cycode/cli/files_collector/sca/npm/restore_pnpm_dependencies.py +2 -3
  65. cycode/cli/files_collector/sca/npm/restore_yarn_dependencies.py +2 -3
  66. cycode/cli/files_collector/sca/npm/workspace/coverage.py +5 -5
  67. cycode/cli/files_collector/sca/npm/workspace/files.py +2 -3
  68. cycode/cli/files_collector/sca/npm/workspace/globs.py +1 -2
  69. cycode/cli/files_collector/sca/npm/workspace/resolvers.py +14 -16
  70. cycode/cli/files_collector/sca/php/restore_composer_dependencies.py +1 -2
  71. cycode/cli/files_collector/sca/python/restore_pip_dependencies.py +2 -3
  72. cycode/cli/files_collector/sca/python/restore_pipenv_dependencies.py +1 -2
  73. cycode/cli/files_collector/sca/python/restore_poetry_dependencies.py +2 -3
  74. cycode/cli/files_collector/sca/python/restore_uv_dependencies.py +2 -3
  75. cycode/cli/files_collector/sca/sca_file_collector.py +5 -5
  76. cycode/cli/files_collector/zip_documents.py +1 -2
  77. cycode/cli/models.py +8 -8
  78. cycode/cli/printers/console_printer.py +9 -9
  79. cycode/cli/printers/json_printer.py +2 -2
  80. cycode/cli/printers/printer_base.py +3 -3
  81. cycode/cli/printers/rich_printer.py +2 -2
  82. cycode/cli/printers/tables/table.py +4 -4
  83. cycode/cli/printers/tables/table_models.py +2 -2
  84. cycode/cli/printers/tables/table_printer_base.py +2 -2
  85. cycode/cli/printers/text_printer.py +4 -4
  86. cycode/cli/printers/utils/code_snippet_syntax.py +1 -1
  87. cycode/cli/printers/utils/detection_data.py +3 -3
  88. cycode/cli/printers/utils/sca_ossf.py +4 -4
  89. cycode/cli/printers/utils/sca_policy_details.py +2 -1
  90. cycode/cli/user_settings/config_file_manager.py +10 -10
  91. cycode/cli/user_settings/configuration_manager.py +6 -6
  92. cycode/cli/user_settings/credentials_manager.py +5 -6
  93. cycode/cli/utils/get_api_client.py +5 -5
  94. cycode/cli/utils/git_proxy.py +8 -8
  95. cycode/cli/utils/host_info.py +21 -38
  96. cycode/cli/utils/ignore_utils.py +13 -15
  97. cycode/cli/utils/jwt_utils.py +2 -4
  98. cycode/cli/utils/path_utils.py +6 -6
  99. cycode/cli/utils/progress_bar.py +4 -4
  100. cycode/cli/utils/scan_batch.py +2 -1
  101. cycode/cli/utils/scan_utils.py +2 -2
  102. cycode/cli/utils/shell_executor.py +3 -4
  103. cycode/cli/utils/task_timer.py +4 -4
  104. cycode/cli/utils/trust_store.py +0 -17
  105. cycode/cli/utils/url_utils.py +1 -2
  106. cycode/cli/utils/version_checker.py +4 -5
  107. cycode/cli/utils/yaml_utils.py +1 -1
  108. cycode/config.py +1 -2
  109. cycode/cyclient/ai_security_manager_client.py +27 -21
  110. cycode/cyclient/auth_client.py +2 -4
  111. cycode/cyclient/base_token_auth_client.py +2 -2
  112. cycode/cyclient/client_creator.py +4 -6
  113. cycode/cyclient/cycode_client_base.py +10 -9
  114. cycode/cyclient/cycode_dev_based_client.py +1 -3
  115. cycode/cyclient/headers.py +1 -2
  116. cycode/cyclient/import_sbom_client.py +3 -4
  117. cycode/cyclient/models.py +88 -30
  118. cycode/cyclient/report_client.py +1 -2
  119. cycode/cyclient/scan_client.py +8 -7
  120. cycode/logger.py +4 -4
  121. {cycode-3.25.1.dev6.dist-info → cycode-3.25.2.dev2.dist-info}/METADATA +12 -19
  122. {cycode-3.25.1.dev6.dist-info → cycode-3.25.2.dev2.dist-info}/RECORD +125 -124
  123. {cycode-3.25.1.dev6.dist-info → cycode-3.25.2.dev2.dist-info}/WHEEL +1 -1
  124. {cycode-3.25.1.dev6.dist-info → cycode-3.25.2.dev2.dist-info}/entry_points.txt +0 -0
  125. {cycode-3.25.1.dev6.dist-info → cycode-3.25.2.dev2.dist-info}/licenses/LICENCE +0 -0
@@ -1,6 +1,6 @@
1
1
  import os
2
2
  from collections.abc import Hashable
3
- from typing import TYPE_CHECKING, Any, Optional, Union
3
+ from typing import TYPE_CHECKING, Any, Union
4
4
 
5
5
  from cycode.cli.consts import CYCODE_CONFIGURATION_DIRECTORY
6
6
  from cycode.cli.user_settings.base_file_manager import BaseFileManager
@@ -30,26 +30,26 @@ class ConfigFileManager(BaseFileManager):
30
30
  def __init__(self, path: Union['Path', str]) -> None:
31
31
  self.path = path
32
32
 
33
- def get_api_url(self) -> Optional[Any]:
33
+ def get_api_url(self) -> Any | None:
34
34
  return self._get_value_from_environment_section(self.API_URL_FIELD_NAME)
35
35
 
36
- def get_app_url(self) -> Optional[Any]:
36
+ def get_app_url(self) -> Any | None:
37
37
  return self._get_value_from_environment_section(self.APP_URL_FIELD_NAME)
38
38
 
39
- def get_verbose_flag(self) -> Optional[Any]:
39
+ def get_verbose_flag(self) -> Any | None:
40
40
  return self._get_value_from_environment_section(self.VERBOSE_FIELD_NAME)
41
41
 
42
42
  def get_exclusions_by_scan_type(self, scan_type: str) -> dict[Hashable, Any]:
43
43
  exclusions_section = self._get_section(self.EXCLUSIONS_SECTION_NAME)
44
44
  return exclusions_section.get(scan_type, {})
45
45
 
46
- def get_max_commits(self, command_scan_type: str) -> Optional[Any]:
46
+ def get_max_commits(self, command_scan_type: str) -> Any | None:
47
47
  return self._get_value_from_command_scan_type_configuration(command_scan_type, self.MAX_COMMITS_FIELD_NAME)
48
48
 
49
- def get_command_timeout(self, command_scan_type: str) -> Optional[Any]:
49
+ def get_command_timeout(self, command_scan_type: str) -> Any | None:
50
50
  return self._get_value_from_command_scan_type_configuration(command_scan_type, self.COMMAND_TIMEOUT_FIELD_NAME)
51
51
 
52
- def get_exclude_detections_in_deleted_lines(self, command_scan_type: str) -> Optional[Any]:
52
+ def get_exclude_detections_in_deleted_lines(self, command_scan_type: str) -> Any | None:
53
53
  return self._get_value_from_command_scan_type_configuration(
54
54
  command_scan_type, self.EXCLUDE_DETECTIONS_IN_DELETED_LINES
55
55
  )
@@ -62,7 +62,7 @@ class ConfigFileManager(BaseFileManager):
62
62
  update_data = {self.ENVIRONMENT_SECTION_NAME: {self.APP_URL_FIELD_NAME: app_url}}
63
63
  self.write_content_to_file(update_data)
64
64
 
65
- def get_installation_id(self) -> Optional[str]:
65
+ def get_installation_id(self) -> str | None:
66
66
  return self._get_value_from_environment_section(self.INSTALLATION_ID_FIELD_NAME)
67
67
 
68
68
  def update_installation_id(self, installation_id: str) -> None:
@@ -103,7 +103,7 @@ class ConfigFileManager(BaseFileManager):
103
103
  scan_type_exclusions = self.get_exclusions_by_scan_type(scan_type)
104
104
  return scan_type_exclusions.get(exclusion_type, [])
105
105
 
106
- def _get_value_from_environment_section(self, field_name: str) -> Optional[Any]:
106
+ def _get_value_from_environment_section(self, field_name: str) -> Any | None:
107
107
  environment_section = self._get_section(self.ENVIRONMENT_SECTION_NAME)
108
108
  return environment_section.get(field_name)
109
109
 
@@ -111,7 +111,7 @@ class ConfigFileManager(BaseFileManager):
111
111
  scan_section = self._get_section(self.SCAN_SECTION_NAME)
112
112
  return scan_section.get(command_scan_type, {})
113
113
 
114
- def _get_value_from_command_scan_type_configuration(self, command_scan_type: str, field_name: str) -> Optional[Any]:
114
+ def _get_value_from_command_scan_type_configuration(self, command_scan_type: str, field_name: str) -> Any | None:
115
115
  command_scan_type_configuration = self._get_scan_configuration_by_scan_type(command_scan_type)
116
116
  return command_scan_type_configuration.get(field_name)
117
117
 
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  from functools import cache
3
3
  from pathlib import Path
4
- from typing import Any, Optional
4
+ from typing import Any
5
5
  from uuid import uuid4
6
6
 
7
7
  from cycode.cli import consts
@@ -59,10 +59,10 @@ class ConfigurationManager:
59
59
  verbose_flag_global_config = self.global_config_file_manager.get_verbose_flag()
60
60
  return verbose_flag_env_var or verbose_flag_local_config or verbose_flag_global_config
61
61
 
62
- def get_api_url_from_environment_variables(self) -> Optional[str]:
62
+ def get_api_url_from_environment_variables(self) -> str | None:
63
63
  return self._get_value_from_environment_variables(consts.CYCODE_API_URL_ENV_VAR_NAME)
64
64
 
65
- def get_app_url_from_environment_variables(self) -> Optional[str]:
65
+ def get_app_url_from_environment_variables(self) -> str | None:
66
66
  return self._get_value_from_environment_variables(consts.CYCODE_APP_URL_ENV_VAR_NAME)
67
67
 
68
68
  def get_verbose_flag_from_environment_variables(self) -> bool:
@@ -94,13 +94,13 @@ class ConfigurationManager:
94
94
 
95
95
  return installation_id
96
96
 
97
- def get_last_reported_activation_version(self, client: str) -> Optional[str]:
97
+ def get_last_reported_activation_version(self, client: str) -> str | None:
98
98
  return self.global_config_file_manager.get_last_reported_activation_versions().get(client)
99
99
 
100
100
  def update_last_reported_activation_version(self, client: str, version: str) -> None:
101
101
  self.global_config_file_manager.update_last_reported_activation_version(client, version)
102
102
 
103
- def get_config_file_manager(self, scope: Optional[str] = None) -> ConfigFileManager:
103
+ def get_config_file_manager(self, scope: str | None = None) -> ConfigFileManager:
104
104
  if scope == 'local':
105
105
  return self.local_config_file_manager
106
106
 
@@ -223,5 +223,5 @@ class ConfigurationManager:
223
223
  return consts.DEFAULT_EXCLUDE_DETECTIONS_IN_DELETED_LINES
224
224
 
225
225
  @staticmethod
226
- def _get_value_from_environment_variables(env_var_name: str, default: Optional[Any] = None) -> Optional[Any]:
226
+ def _get_value_from_environment_variables(env_var_name: str, default: Any | None = None) -> Any | None:
227
227
  return os.getenv(env_var_name, default)
@@ -1,6 +1,5 @@
1
1
  import os
2
2
  from pathlib import Path
3
- from typing import Optional
4
3
 
5
4
  from cycode.cli.config import (
6
5
  CYCODE_CLIENT_ID_ENV_VAR_NAME,
@@ -36,19 +35,19 @@ class CredentialsManager(BaseFileManager):
36
35
  client_secret = os.getenv(CYCODE_CLIENT_SECRET_ENV_VAR_NAME)
37
36
  return client_id, client_secret
38
37
 
39
- def get_credentials_from_file(self) -> tuple[Optional[str], Optional[str]]:
38
+ def get_credentials_from_file(self) -> tuple[str | None, str | None]:
40
39
  file_content = self.read_file()
41
40
  client_id = file_content.get(self.CLIENT_ID_FIELD_NAME)
42
41
  client_secret = file_content.get(self.CLIENT_SECRET_FIELD_NAME)
43
42
  return client_id, client_secret
44
43
 
45
- def get_oidc_credentials_from_file(self) -> tuple[Optional[str], Optional[str]]:
44
+ def get_oidc_credentials_from_file(self) -> tuple[str | None, str | None]:
46
45
  file_content = self.read_file()
47
46
  client_id = file_content.get(self.CLIENT_ID_FIELD_NAME)
48
47
  id_token = file_content.get(self.ID_TOKEN_FIELD_NAME)
49
48
  return client_id, id_token
50
49
 
51
- def get_oidc_credentials(self) -> tuple[Optional[str], Optional[str]]:
50
+ def get_oidc_credentials(self) -> tuple[str | None, str | None]:
52
51
  client_id = os.getenv(CYCODE_CLIENT_ID_ENV_VAR_NAME)
53
52
  id_token = os.getenv(CYCODE_ID_TOKEN_ENV_VAR_NAME)
54
53
 
@@ -65,7 +64,7 @@ class CredentialsManager(BaseFileManager):
65
64
  file_content_to_update = {self.CLIENT_ID_FIELD_NAME: client_id, self.CLIENT_SECRET_FIELD_NAME: client_secret}
66
65
  self.write_content_to_file(file_content_to_update)
67
66
 
68
- def get_access_token(self) -> tuple[Optional[str], Optional[float], Optional[JwtCreator]]:
67
+ def get_access_token(self) -> tuple[str | None, float | None, JwtCreator | None]:
69
68
  file_content = self.read_file()
70
69
 
71
70
  access_token = file_content.get(self.ACCESS_TOKEN_FIELD_NAME)
@@ -79,7 +78,7 @@ class CredentialsManager(BaseFileManager):
79
78
  return access_token, expires_in, creator
80
79
 
81
80
  def update_access_token(
82
- self, access_token: Optional[str], expires_in: Optional[float], creator: Optional[JwtCreator]
81
+ self, access_token: str | None, expires_in: float | None, creator: JwtCreator | None
83
82
  ) -> None:
84
83
  file_content_to_update = {
85
84
  self.ACCESS_TOKEN_FIELD_NAME: access_token,
@@ -1,4 +1,4 @@
1
- from typing import TYPE_CHECKING, Optional, Union
1
+ from typing import TYPE_CHECKING, Union
2
2
 
3
3
  import click
4
4
 
@@ -21,10 +21,10 @@ if TYPE_CHECKING:
21
21
 
22
22
  def _get_cycode_client(
23
23
  create_client_func: callable,
24
- client_id: Optional[str],
25
- client_secret: Optional[str],
24
+ client_id: str | None,
25
+ client_secret: str | None,
26
26
  hide_response_log: bool,
27
- id_token: Optional[str] = None,
27
+ id_token: str | None = None,
28
28
  ) -> Union['ScanClient', 'ReportClient', 'ImportSbomClient', 'AISecurityManagerClient']:
29
29
  if client_id and id_token:
30
30
  return create_client_func(client_id, None, hide_response_log, id_token)
@@ -80,6 +80,6 @@ def _get_configured_credentials() -> tuple[str, str]:
80
80
  return credentials_manager.get_credentials()
81
81
 
82
82
 
83
- def _get_configured_oidc_credentials() -> tuple[Optional[str], Optional[str]]:
83
+ def _get_configured_oidc_credentials() -> tuple[str | None, str | None]:
84
84
  credentials_manager = CredentialsManager()
85
85
  return credentials_manager.get_oidc_credentials()
@@ -1,7 +1,7 @@
1
1
  import types
2
2
  from abc import ABC, abstractmethod
3
3
  from functools import cache
4
- from typing import TYPE_CHECKING, Optional
4
+ from typing import TYPE_CHECKING
5
5
 
6
6
  _GIT_ERROR_MESSAGE = """
7
7
  Cycode CLI needs the Git executable to be installed on the system.
@@ -21,7 +21,7 @@ class GitProxyError(Exception):
21
21
 
22
22
  # GitPython runs `git version` on import, so it is imported on first use rather than at CLI startup
23
23
  @cache
24
- def _import_git() -> Optional[types.ModuleType]:
24
+ def _import_git() -> types.ModuleType | None:
25
25
  try:
26
26
  import git
27
27
  except ImportError:
@@ -31,7 +31,7 @@ def _import_git() -> Optional[types.ModuleType]:
31
31
 
32
32
  class _AbstractGitProxy(ABC):
33
33
  @abstractmethod
34
- def get_repo(self, path: Optional['PathLike'] = None, *args, **kwargs) -> 'Repo': ...
34
+ def get_repo(self, path: 'PathLike | None' = None, *args, **kwargs) -> 'Repo': ...
35
35
 
36
36
  @abstractmethod
37
37
  def get_null_tree(self) -> object: ...
@@ -44,7 +44,7 @@ class _AbstractGitProxy(ABC):
44
44
 
45
45
 
46
46
  class _DummyGitProxy(_AbstractGitProxy):
47
- def get_repo(self, path: Optional['PathLike'] = None, *args, **kwargs) -> 'Repo':
47
+ def get_repo(self, path: 'PathLike | None' = None, *args, **kwargs) -> 'Repo':
48
48
  raise RuntimeError(_GIT_ERROR_MESSAGE)
49
49
 
50
50
  def get_null_tree(self) -> object:
@@ -61,7 +61,7 @@ class _GitProxy(_AbstractGitProxy):
61
61
  def __init__(self, git_module: types.ModuleType) -> None:
62
62
  self._git = git_module
63
63
 
64
- def get_repo(self, path: Optional['PathLike'] = None, *args, **kwargs) -> 'Repo':
64
+ def get_repo(self, path: 'PathLike | None' = None, *args, **kwargs) -> 'Repo':
65
65
  return self._git.Repo(path, *args, **kwargs)
66
66
 
67
67
  def get_null_tree(self) -> object:
@@ -74,7 +74,7 @@ class _GitProxy(_AbstractGitProxy):
74
74
  return self._git.GitCommandError
75
75
 
76
76
 
77
- def get_git_proxy(git_module: Optional[types.ModuleType]) -> _AbstractGitProxy:
77
+ def get_git_proxy(git_module: types.ModuleType | None) -> _AbstractGitProxy:
78
78
  return _GitProxy(git_module) if git_module else _DummyGitProxy()
79
79
 
80
80
 
@@ -82,7 +82,7 @@ class GitProxyManager(_AbstractGitProxy):
82
82
  """We are using this manager for easy unit testing and mocking of the git module."""
83
83
 
84
84
  def __init__(self) -> None:
85
- self._git_proxy: Optional[_AbstractGitProxy] = None
85
+ self._git_proxy: _AbstractGitProxy | None = None
86
86
 
87
87
  def _get_git_proxy(self) -> _AbstractGitProxy:
88
88
  if self._git_proxy is None:
@@ -95,7 +95,7 @@ class GitProxyManager(_AbstractGitProxy):
95
95
  def _set_git_proxy(self) -> None:
96
96
  self._git_proxy = _GitProxy(_import_git())
97
97
 
98
- def get_repo(self, path: Optional['PathLike'] = None, *args, **kwargs) -> 'Repo':
98
+ def get_repo(self, path: 'PathLike | None' = None, *args, **kwargs) -> 'Repo':
99
99
  return self._get_git_proxy().get_repo(path, *args, **kwargs)
100
100
 
101
101
  def get_null_tree(self) -> object:
@@ -7,15 +7,15 @@ import subprocess
7
7
  import sys
8
8
  import tempfile
9
9
  from pathlib import Path
10
- from typing import Any, Optional
10
+ from typing import Any
11
11
 
12
12
  from cycode.cli.consts import CYCODE_CONFIGURATION_DIRECTORY
13
13
  from cycode.logger import get_logger
14
14
 
15
15
  logger = get_logger('HOST INFO')
16
16
 
17
- pythoncom: Optional[Any] = None
18
- win32com_client: Optional[Any] = None
17
+ pythoncom: Any | None = None
18
+ win32com_client: Any | None = None
19
19
  if sys.platform == 'win32':
20
20
  try:
21
21
  import pythoncom
@@ -30,31 +30,22 @@ _DEVICE_ID_CACHE_FILE_NAME = 'device-id'
30
30
  _PLATFORM_NAMES = {'Darwin': 'macOS', 'Windows': 'Windows', 'Linux': 'Linux'}
31
31
 
32
32
 
33
- def _run(command: list, timeout: int = _SUBPROCESS_TIMEOUT_SEC) -> Optional[str]:
33
+ def _run(command: list, timeout: int = _SUBPROCESS_TIMEOUT_SEC) -> str | None:
34
34
  """Run a command and return its stripped stdout. Never raises; returns None on any error."""
35
35
  try:
36
- result = subprocess.run(command, capture_output=True, text=True, timeout=timeout) # noqa: S603
36
+ result = subprocess.run(command, capture_output=True, text=True, timeout=timeout, check=False) # noqa: S603
37
37
  return result.stdout.strip() or None
38
38
  except Exception as e:
39
39
  logger.debug('Failed to run command %s', command, exc_info=e)
40
40
  return None
41
41
 
42
42
 
43
- def _read_text_file(path: str) -> Optional[str]:
44
- """Read and strip a text file. Never raises; returns None if it can't be read."""
45
- try:
46
- with open(path) as text_file:
47
- return text_file.read().strip() or None
48
- except OSError:
49
- return None
50
-
51
-
52
43
  def is_64bit() -> bool:
53
44
  """Whether the running Python interpreter is 64-bit (not the OS)."""
54
45
  return sys.maxsize > 2**32
55
46
 
56
47
 
57
- def get_hostname() -> Optional[str]:
48
+ def get_hostname() -> str | None:
58
49
  try:
59
50
  return socket.gethostname() or None
60
51
  except Exception as e:
@@ -62,7 +53,7 @@ def get_hostname() -> Optional[str]:
62
53
  return None
63
54
 
64
55
 
65
- def get_platform_name() -> Optional[str]:
56
+ def get_platform_name() -> str | None:
66
57
  try:
67
58
  system = platform.system()
68
59
  return _PLATFORM_NAMES.get(system, system or None)
@@ -71,7 +62,7 @@ def get_platform_name() -> Optional[str]:
71
62
  return None
72
63
 
73
64
 
74
- def get_os_version() -> Optional[str]:
65
+ def get_os_version() -> str | None:
75
66
  try:
76
67
  system = platform.system()
77
68
  if system == 'Darwin':
@@ -86,26 +77,18 @@ def get_os_version() -> Optional[str]:
86
77
  return None
87
78
 
88
79
 
89
- def _get_linux_os_version() -> Optional[str]:
90
- freedesktop_os_release = getattr(platform, 'freedesktop_os_release', None) # Python 3.10+
91
- if freedesktop_os_release is not None:
92
- try:
93
- version_id = freedesktop_os_release().get('VERSION_ID')
94
- if version_id:
95
- return version_id
96
- except OSError:
97
- pass
98
-
99
- os_release = _read_text_file('/etc/os-release') # Python 3.9 fallback: parse manually
100
- if os_release:
101
- for line in os_release.splitlines():
102
- if line.startswith('VERSION_ID='):
103
- return line.split('=', 1)[1].strip().strip('"') or None
80
+ def _get_linux_os_version() -> str | None:
81
+ try:
82
+ version_id = platform.freedesktop_os_release().get('VERSION_ID')
83
+ if version_id:
84
+ return version_id
85
+ except OSError:
86
+ pass
104
87
 
105
88
  return platform.release() or None
106
89
 
107
90
 
108
- def get_last_login_user() -> Optional[str]:
91
+ def get_last_login_user() -> str | None:
109
92
  try:
110
93
  return getpass.getuser() or None
111
94
  except Exception as e:
@@ -113,7 +96,7 @@ def get_last_login_user() -> Optional[str]:
113
96
  return None
114
97
 
115
98
 
116
- def get_serial_number() -> Optional[str]:
99
+ def get_serial_number() -> str | None:
117
100
  # The serial is immutable hardware info, but resolving it shells out (ioreg/WMI)
118
101
  # and this runs in a fresh process per AI hook event - cache it on disk.
119
102
  cached = _read_serial_number_cache()
@@ -126,7 +109,7 @@ def get_serial_number() -> Optional[str]:
126
109
  return serial
127
110
 
128
111
 
129
- def _resolve_serial_number() -> Optional[str]:
112
+ def _resolve_serial_number() -> str | None:
130
113
  try:
131
114
  system = platform.system()
132
115
  if system == 'Darwin':
@@ -142,7 +125,7 @@ def _serial_number_cache_path() -> Path:
142
125
  return Path.home() / CYCODE_CONFIGURATION_DIRECTORY / _DEVICE_ID_CACHE_FILE_NAME
143
126
 
144
127
 
145
- def _read_serial_number_cache() -> Optional[str]:
128
+ def _read_serial_number_cache() -> str | None:
146
129
  try:
147
130
  return _serial_number_cache_path().read_text(encoding='utf-8').strip() or None
148
131
  except Exception:
@@ -170,7 +153,7 @@ def _write_serial_number_cache(serial: str) -> None:
170
153
  logger.debug('Failed to cache serial number', exc_info=e)
171
154
 
172
155
 
173
- def _get_macos_serial_number() -> Optional[str]:
156
+ def _get_macos_serial_number() -> str | None:
174
157
  output = _run(['ioreg', '-c', 'IOPlatformExpertDevice', '-d', '2'])
175
158
  if not output:
176
159
  return None
@@ -178,7 +161,7 @@ def _get_macos_serial_number() -> Optional[str]:
178
161
  return match.group(1) if match else None
179
162
 
180
163
 
181
- def _get_windows_serial_number() -> Optional[str]:
164
+ def _get_windows_serial_number() -> str | None:
182
165
  """Read the OEM serial over WMI."""
183
166
  if pythoncom is None or win32com_client is None:
184
167
  return None
@@ -43,8 +43,6 @@ from os import PathLike
43
43
  from typing import (
44
44
  Any,
45
45
  BinaryIO,
46
- Optional,
47
- Union,
48
46
  )
49
47
 
50
48
 
@@ -205,9 +203,9 @@ class Pattern:
205
203
  class IgnoreFilter:
206
204
  def __init__(
207
205
  self,
208
- patterns: Iterable[Union[str, bytes]],
206
+ patterns: Iterable[str | bytes],
209
207
  ignore_case: bool = False,
210
- path: Optional[Union[PathLike, str]] = None,
208
+ path: PathLike | str | None = None,
211
209
  ) -> None:
212
210
  if hasattr(path, '__fspath__'):
213
211
  path = path.__fspath__()
@@ -227,13 +225,13 @@ class IgnoreFilter:
227
225
  d['path'] = path
228
226
  return d
229
227
 
230
- def append_pattern(self, pattern: Union[str, bytes]) -> None:
228
+ def append_pattern(self, pattern: str | bytes) -> None:
231
229
  """Add a pattern to the set."""
232
230
  if isinstance(pattern, str):
233
231
  pattern = bytes(pattern, 'utf-8')
234
232
  self._patterns.append(Pattern(pattern, self._ignore_case))
235
233
 
236
- def find_matching(self, path: Union[bytes, str]) -> Iterable[Pattern]:
234
+ def find_matching(self, path: bytes | str) -> Iterable[Pattern]:
237
235
  """Yield all matching patterns for path.
238
236
 
239
237
  Args:
@@ -248,7 +246,7 @@ class IgnoreFilter:
248
246
  if pattern.match(path):
249
247
  yield pattern
250
248
 
251
- def is_ignored(self, path: Union[bytes, str]) -> Optional[bool]:
249
+ def is_ignored(self, path: bytes | str) -> bool | None:
252
250
  """Check whether a path is ignored.
253
251
 
254
252
  For directories, include a trailing slash.
@@ -264,7 +262,7 @@ class IgnoreFilter:
264
262
  return status
265
263
 
266
264
  @classmethod
267
- def from_path(cls, path: Union[PathLike, str], ignore_case: bool = False) -> 'IgnoreFilter':
265
+ def from_path(cls, path: PathLike | str, ignore_case: bool = False) -> 'IgnoreFilter':
268
266
  if hasattr(path, '__fspath__'):
269
267
  path = path.__fspath__()
270
268
  with open(path, 'rb') as f:
@@ -284,7 +282,7 @@ class IgnoreFilterManager:
284
282
  self,
285
283
  path: str,
286
284
  global_filters: list[IgnoreFilter],
287
- ignore_file_name: Optional[str] = None,
285
+ ignore_file_name: str | None = None,
288
286
  ignore_case: bool = False,
289
287
  ) -> None:
290
288
  if hasattr(path, '__fspath__'):
@@ -317,14 +315,14 @@ class IgnoreFilterManager:
317
315
  return self._top_path
318
316
 
319
317
  @property
320
- def ignore_file_name(self) -> Optional[str]:
318
+ def ignore_file_name(self) -> str | None:
321
319
  return self._ignore_file_name
322
320
 
323
321
  @property
324
322
  def ignore_case(self) -> bool:
325
323
  return self._ignore_case
326
324
 
327
- def _load_path(self, path: str) -> Optional[IgnoreFilter]:
325
+ def _load_path(self, path: str) -> IgnoreFilter | None:
328
326
  try:
329
327
  return self._path_filters[path]
330
328
  except KeyError:
@@ -370,7 +368,7 @@ class IgnoreFilterManager:
370
368
  filters.insert(0, (i, ignore_filter))
371
369
  return iter(matches)
372
370
 
373
- def is_ignored(self, path: str) -> Optional[bool]:
371
+ def is_ignored(self, path: str) -> bool | None:
374
372
  """Check whether a path is ignored.
375
373
 
376
374
  Args:
@@ -425,9 +423,9 @@ class IgnoreFilterManager:
425
423
  def build(
426
424
  cls,
427
425
  path: str,
428
- global_ignore_file_paths: Optional[Iterable[str]] = None,
429
- global_patterns: Optional[Iterable[Union[str, bytes]]] = None,
430
- ignore_file_name: Optional[str] = None,
426
+ global_ignore_file_paths: Iterable[str] | None = None,
427
+ global_patterns: Iterable[str | bytes] | None = None,
428
+ ignore_file_name: str | None = None,
431
429
  ignore_case: bool = False,
432
430
  ) -> 'IgnoreFilterManager':
433
431
  """Create a IgnoreFilterManager from patterns and paths.
@@ -1,11 +1,9 @@
1
- from typing import Optional
2
-
3
1
  import jwt
4
2
 
5
3
  _JWT_PAYLOAD_POSSIBLE_USER_ID_FIELD_NAMES = ('userId', 'internalId', 'token-user-id')
6
4
 
7
5
 
8
- def decode_jwt_unverified(token: str) -> Optional[dict]:
6
+ def decode_jwt_unverified(token: str) -> dict | None:
9
7
  """Return JWT claims without signature verification, or None if the token is unreadable."""
10
8
  try:
11
9
  return jwt.decode(token, options={'verify_signature': False})
@@ -13,7 +11,7 @@ def decode_jwt_unverified(token: str) -> Optional[dict]:
13
11
  return None
14
12
 
15
13
 
16
- def get_user_and_tenant_ids_from_access_token(access_token: str) -> tuple[Optional[str], Optional[str]]:
14
+ def get_user_and_tenant_ids_from_access_token(access_token: str) -> tuple[str | None, str | None]:
17
15
  payload = jwt.decode(access_token, options={'verify_signature': False})
18
16
 
19
17
  user_id = None
@@ -3,7 +3,7 @@ import os
3
3
  import tempfile
4
4
  from functools import cache
5
5
  from pathlib import PurePath, PurePosixPath, PureWindowsPath
6
- from typing import TYPE_CHECKING, AnyStr, Optional, Union
6
+ from typing import TYPE_CHECKING, AnyStr, Union
7
7
 
8
8
  import typer
9
9
 
@@ -31,7 +31,7 @@ def get_absolute_path(path: str) -> str:
31
31
  return os.path.abspath(path)
32
32
 
33
33
 
34
- def _get_starting_chunk(filename: str, length: int = 1024) -> Optional[bytes]:
34
+ def _get_starting_chunk(filename: str, length: int = 1024) -> bytes | None:
35
35
  # We are using our own implementation of get_starting_chunk
36
36
  # because the original one from binaryornot uses print()...
37
37
 
@@ -79,7 +79,7 @@ def join_paths(path: str, filename: str) -> str:
79
79
  return os.path.join(path, filename)
80
80
 
81
81
 
82
- def get_file_content(file_path: Union[str, 'PathLike']) -> Optional[AnyStr]:
82
+ def get_file_content(file_path: Union[str, 'PathLike']) -> AnyStr | None:
83
83
  try:
84
84
  with open(file_path, encoding='UTF-8') as f:
85
85
  return f.read()
@@ -115,7 +115,7 @@ def quarantine_corrupt_file(filename: str) -> None:
115
115
  logger.warning('Failed to quarantine corrupt file, %s', {'filename': filename}, exc_info=e)
116
116
 
117
117
 
118
- def load_json(txt: str) -> Optional[dict]:
118
+ def load_json(txt: str) -> dict | None:
119
119
  try:
120
120
  return json.loads(txt)
121
121
  except json.JSONDecodeError:
@@ -152,7 +152,7 @@ def concat_unique_id(filename: str, unique_id: str) -> str:
152
152
  return str(PurePosixPath(unique_id, *_to_relative_posix_parts(filename)))
153
153
 
154
154
 
155
- def _as_scan_root(value: object) -> Optional[str]:
155
+ def _as_scan_root(value: object) -> str | None:
156
156
  if isinstance(value, (str, os.PathLike)):
157
157
  return os.fspath(value) or None
158
158
 
@@ -181,7 +181,7 @@ def get_scan_roots_from_context(ctx: typer.Context) -> tuple[str, ...]:
181
181
  return tuple(dict.fromkeys(scan_roots))
182
182
 
183
183
 
184
- def get_path_from_context(ctx: typer.Context) -> Optional[str]:
184
+ def get_path_from_context(ctx: typer.Context) -> str | None:
185
185
  scan_roots = get_scan_roots_from_context(ctx)
186
186
  return scan_roots[0] if scan_roots else None
187
187
 
@@ -1,6 +1,6 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from enum import auto
3
- from typing import NamedTuple, Optional
3
+ from typing import NamedTuple
4
4
 
5
5
  from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn, TimeElapsedColumn
6
6
 
@@ -109,7 +109,7 @@ class BaseProgressBar(ABC):
109
109
  def update(self, section: 'ProgressBarSection') -> None: ...
110
110
 
111
111
  @abstractmethod
112
- def update_right_side_label(self, label: Optional[str] = None) -> None: ...
112
+ def update_right_side_label(self, label: str | None = None) -> None: ...
113
113
 
114
114
 
115
115
  class DummyProgressBar(BaseProgressBar):
@@ -128,7 +128,7 @@ class DummyProgressBar(BaseProgressBar):
128
128
  def update(self, section: 'ProgressBarSection') -> None:
129
129
  pass
130
130
 
131
- def update_right_side_label(self, label: Optional[str] = None) -> None:
131
+ def update_right_side_label(self, label: str | None = None) -> None:
132
132
  pass
133
133
 
134
134
 
@@ -240,7 +240,7 @@ class CompositeProgressBar(BaseProgressBar):
240
240
  self._increment_progress(section)
241
241
  self._maybe_update_current_section()
242
242
 
243
- def update_right_side_label(self, label: Optional[str] = None) -> None:
243
+ def update_right_side_label(self, label: str | None = None) -> None:
244
244
  self._current_right_side_label = f'({label})' if label else ''
245
245
  self._progress_bar_update()
246
246
 
@@ -1,6 +1,7 @@
1
1
  import os
2
+ from collections.abc import Callable
2
3
  from multiprocessing.pool import ThreadPool
3
- from typing import TYPE_CHECKING, Callable
4
+ from typing import TYPE_CHECKING
4
5
 
5
6
  from cycode.cli import consts
6
7
  from cycode.cli.models import Document
@@ -1,5 +1,5 @@
1
1
  import os
2
- from typing import TYPE_CHECKING, Optional
2
+ from typing import TYPE_CHECKING
3
3
  from uuid import UUID, uuid4
4
4
 
5
5
  import typer
@@ -26,7 +26,7 @@ def is_scan_failed(ctx: typer.Context) -> bool:
26
26
 
27
27
 
28
28
  def is_cycodeignore_allowed_by_scan_config(ctx: typer.Context) -> bool:
29
- scan_config: Optional[ScanConfiguration] = ctx.obj.get('scan_config')
29
+ scan_config: ScanConfiguration | None = ctx.obj.get('scan_config')
30
30
  return scan_config.is_cycode_ignore_allowed if scan_config else True
31
31
 
32
32
 
@@ -1,6 +1,5 @@
1
1
  import subprocess
2
2
  import time
3
- from typing import Optional, Union
4
3
 
5
4
  import click
6
5
  import typer
@@ -14,11 +13,11 @@ logger = get_logger('SHELL')
14
13
 
15
14
 
16
15
  def shell(
17
- command: Union[str, list[str]],
16
+ command: str | list[str],
18
17
  timeout: int = _SUBPROCESS_DEFAULT_TIMEOUT_SEC,
19
- working_directory: Optional[str] = None,
18
+ working_directory: str | None = None,
20
19
  silent_exc_info: bool = False,
21
- ) -> Optional[str]:
20
+ ) -> str | None:
22
21
  logger.debug('Executing shell command: %s', command)
23
22
 
24
23
  try: