cycode 3.16.1.dev7__py3-none-any.whl → 3.16.1.dev8__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.
cycode/__init__.py CHANGED
@@ -1 +1,8 @@
1
- __version__ = '3.16.1.dev7' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
1
+ import time as _time
2
+
3
+ # Unix-epoch wall clock captured at the earliest possible moment of CLI
4
+ # startup. Sent as `scan_parameters.cli_start_time` so the server can compute
5
+ # end-to-end scan duration from the moment the user actually triggered it.
6
+ _BOOT_WALL: float = _time.time()
7
+
8
+ __version__ = '3.16.1.dev8' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
cycode/cli/app.py CHANGED
@@ -1,3 +1,4 @@
1
+ import importlib
1
2
  import logging
2
3
  import sys
3
4
  from typing import Annotated, Optional
@@ -10,12 +11,7 @@ from typer._completion_shared import Shells
10
11
  from typer.completion import install_callback, show_callback
11
12
 
12
13
  from cycode import __version__
13
- from cycode.cli.apps import ai_guardrails, ai_remediation, auth, configure, ignore, report, report_import, scan, status
14
14
  from cycode.cli.apps.api import get_platform_group
15
-
16
- if sys.version_info >= (3, 10):
17
- from cycode.cli.apps import mcp
18
-
19
15
  from cycode.cli.cli_types import OutputTypeOption
20
16
  from cycode.cli.consts import CLI_CONTEXT_SETTINGS
21
17
  from cycode.cli.printers import ConsolePrinter
@@ -46,17 +42,88 @@ app = typer.Typer(
46
42
  add_completion=False, # we add it manually to control the rich help panel
47
43
  )
48
44
 
49
- app.add_typer(ai_guardrails.app)
50
- app.add_typer(ai_remediation.app)
51
- app.add_typer(auth.app)
52
- app.add_typer(configure.app)
53
- app.add_typer(ignore.app)
54
- app.add_typer(report.app)
55
- app.add_typer(report_import.app)
56
- app.add_typer(scan.app)
57
- app.add_typer(status.app)
45
+ # Top-level subcommand → module providing its Typer app. Peeking at sys.argv
46
+ # lets us import only the invoked subapp on the hot path (e.g.
47
+ # `cycode ai-guardrails scan`), skipping ~300ms of unrelated imports.
48
+ _SUBAPP_MODULES: dict[str, str] = {
49
+ 'ai-guardrails': 'cycode.cli.apps.ai_guardrails',
50
+ 'ai-remediation': 'cycode.cli.apps.ai_remediation',
51
+ 'auth': 'cycode.cli.apps.auth',
52
+ 'configure': 'cycode.cli.apps.configure',
53
+ 'ignore': 'cycode.cli.apps.ignore',
54
+ 'report': 'cycode.cli.apps.report',
55
+ 'import': 'cycode.cli.apps.report_import',
56
+ 'scan': 'cycode.cli.apps.scan',
57
+ 'status': 'cycode.cli.apps.status',
58
+ }
58
59
  if sys.version_info >= (3, 10):
59
- app.add_typer(mcp.app)
60
+ _SUBAPP_MODULES['mcp'] = 'cycode.cli.apps.mcp'
61
+
62
+ # Aliases: alternate spellings that resolve to a primary subcommand key.
63
+ _SUBAPP_ALIASES: dict[str, str] = {
64
+ 'ai_remediation': 'ai-remediation', # backward-compat underscore form
65
+ 'version': 'status',
66
+ }
67
+
68
+ # Root-level options that consume a following value; argv-peek must skip past
69
+ # both the option and its value when scanning for the first positional arg.
70
+ _ROOT_OPTS_WITH_VALUE = frozenset(
71
+ {
72
+ '--output',
73
+ '-o',
74
+ '--user-agent',
75
+ '--client-secret',
76
+ '--client-id',
77
+ '--id-token',
78
+ '--show-completion',
79
+ }
80
+ )
81
+
82
+
83
+ def _detect_invocation() -> tuple[Optional[str], Optional[str]]:
84
+ """Return (top-level-subapp, second-level-subcommand) parsed from sys.argv.
85
+
86
+ Both values may be None: when no positional arg matches a known subapp,
87
+ or when the user only provided a top-level subcommand.
88
+ """
89
+ positionals = []
90
+ args = sys.argv[1:]
91
+ i = 0
92
+ while i < len(args):
93
+ arg = args[i]
94
+ if arg in _ROOT_OPTS_WITH_VALUE:
95
+ i += 2
96
+ elif arg.startswith('-'):
97
+ # Any flag form: short, long, --key=value, or '--' marker. Skip the token only.
98
+ i += 1
99
+ else:
100
+ positionals.append(arg)
101
+ if len(positionals) >= 2:
102
+ break
103
+ i += 1
104
+ subapp = positionals[0] if positionals else None
105
+ subapp = _SUBAPP_ALIASES.get(subapp, subapp)
106
+ if subapp not in _SUBAPP_MODULES:
107
+ return None, None
108
+ subcommand = positionals[1] if len(positionals) >= 2 else None
109
+ return subapp, subcommand
110
+
111
+
112
+ # Computed once at import; reused by lazy registration and the version-checker skip.
113
+ _INVOKED_SUBAPP, _INVOKED_SUBCOMMAND = _detect_invocation()
114
+
115
+
116
+ def _register_subapps(only: Optional[str]) -> None:
117
+ if only is not None:
118
+ app.add_typer(importlib.import_module(_SUBAPP_MODULES[only]).app)
119
+ return
120
+ # Cold path (--help, completion, unknown subcommand): load all modules so
121
+ # root help lists everything. Deduplicate since aliases share modules.
122
+ for module_path in dict.fromkeys(_SUBAPP_MODULES.values()):
123
+ app.add_typer(importlib.import_module(module_path).app)
124
+
125
+
126
+ _register_subapps(_INVOKED_SUBAPP)
60
127
 
61
128
  # Register the `platform` command group (dynamically built from the OpenAPI spec).
62
129
  # The group itself is constructed cheaply at import time; the spec is only fetched
@@ -81,6 +148,12 @@ typer.main.get_group = _get_group_with_platform
81
148
 
82
149
 
83
150
  def check_latest_version_on_close(ctx: typer.Context) -> None:
151
+ # Skip on `cycode ai-guardrails scan` — it emits JSON to stdout, so an
152
+ # upgrade notice would corrupt the response. Human-driven sibling commands
153
+ # (install, uninstall, status, session-start) still get the notice.
154
+ if (_INVOKED_SUBAPP, _INVOKED_SUBCOMMAND) == ('ai-guardrails', 'scan'):
155
+ return
156
+
84
157
  output = ctx.obj.get('output')
85
158
  # don't print anything if the output is JSON
86
159
  if output == OutputTypeOption.JSON:
@@ -204,18 +204,21 @@ def _get_scan_documents_thread_func(
204
204
  'zip_file_size': zip_file_size,
205
205
  },
206
206
  )
207
- report_scan_status(
208
- cycode_client,
209
- scan_type,
210
- scan_id,
211
- scan_completed,
212
- relevant_detections_count,
213
- detections_count,
214
- len(batch),
215
- zip_file_size,
216
- command_scan_type,
217
- error_message,
218
- )
207
+ # Sync flows already received the full result inline; only async flows
208
+ # need a separate status report to signal polling completion.
209
+ if not should_use_sync_flow:
210
+ report_scan_status(
211
+ cycode_client,
212
+ scan_type,
213
+ scan_id,
214
+ scan_completed,
215
+ relevant_detections_count,
216
+ detections_count,
217
+ len(batch),
218
+ zip_file_size,
219
+ command_scan_type,
220
+ error_message,
221
+ )
219
222
 
220
223
  return scan_id, error, local_scan_result
221
224
 
@@ -2,6 +2,7 @@ from typing import Optional
2
2
 
3
3
  import typer
4
4
 
5
+ from cycode import _BOOT_WALL
5
6
  from cycode.cli.apps.scan.remote_url_resolver import get_remote_url_scan_parameter
6
7
  from cycode.cli.utils.scan_utils import generate_unique_scan_id
7
8
  from cycode.logger import get_logger
@@ -17,6 +18,7 @@ def _get_default_scan_parameters(ctx: typer.Context) -> dict:
17
18
  'license_compliance': ctx.obj.get('license-compliance'),
18
19
  'command_type': ctx.info_name.replace('-', '_'), # save backward compatibility
19
20
  'aggregation_id': str(generate_unique_scan_id()),
21
+ 'cli_start_time': _BOOT_WALL,
20
22
  }
21
23
 
22
24
 
@@ -189,6 +189,10 @@ def enrich_scan_result_with_data_from_detection_rules(
189
189
  for detection in detections_per_file.detections:
190
190
  detection_rule_ids.add(detection.detection_rule_id)
191
191
 
192
+ if not detection_rule_ids:
193
+ logger.debug('No detections to enrich, skipping detection_rules fetch')
194
+ return
195
+
192
196
  detection_rules = cycode_client.get_detection_rules(detection_rule_ids)
193
197
  detection_rules_by_id = {detection_rule.detection_rule_id: detection_rule for detection_rule in detection_rules}
194
198
 
@@ -24,19 +24,10 @@ class BaseTokenAuthClient(CycodeClient, ABC):
24
24
  self.client_id = client_id
25
25
 
26
26
  self._credentials_manager = CredentialsManager()
27
- # load cached access token
28
- access_token, expires_in, creator = self._credentials_manager.get_access_token()
29
-
30
- self._access_token = self._expires_in = None
31
- expected_creator = self._create_jwt_creator()
32
- if creator == expected_creator:
33
- # we must be sure that cached access token is created using the same client id and client secret.
34
- # because client id and client secret could be passed via command, via env vars or via config file.
35
- # we must not use cached access token if client id or client secret was changed.
36
- self._access_token = access_token
37
- self._expires_in = arrow.get(expires_in) if expires_in else None
38
-
27
+ self._access_token = None
28
+ self._expires_in = None
39
29
  self._lock = Lock()
30
+ self._load_token_from_disk()
40
31
 
41
32
  def get_access_token(self) -> str:
42
33
  with self._lock:
@@ -51,8 +42,30 @@ class BaseTokenAuthClient(CycodeClient, ABC):
51
42
  self._credentials_manager.update_access_token(None, None, None)
52
43
 
53
44
  def refresh_access_token_if_needed(self) -> None:
54
- if self._access_token is None or self._expires_in is None or arrow.utcnow() >= self._expires_in:
55
- self.refresh_access_token()
45
+ if self._has_valid_token():
46
+ return
47
+ # Re-check disk before doing the network refresh: another client instance
48
+ # in this process may have already refreshed and persisted a fresh token.
49
+ self._load_token_from_disk()
50
+ if self._has_valid_token():
51
+ return
52
+ self.refresh_access_token()
53
+
54
+ def _has_valid_token(self) -> bool:
55
+ return self._access_token is not None and self._expires_in is not None and arrow.utcnow() < self._expires_in
56
+
57
+ def _load_token_from_disk(self) -> None:
58
+ access_token, expires_in, creator = self._credentials_manager.get_access_token()
59
+ expected_creator = self._create_jwt_creator()
60
+ # We must be sure that cached access token is created using the same client id and client secret.
61
+ # Because client id and client secret could be passed via command, via env vars or via config file.
62
+ # We must not use cached access token if client id or client secret was changed.
63
+ if creator == expected_creator and access_token:
64
+ self._access_token = access_token
65
+ self._expires_in = arrow.get(expires_in) if expires_in else None
66
+ else:
67
+ self._access_token = None
68
+ self._expires_in = None
56
69
 
57
70
  def refresh_access_token(self) -> None:
58
71
  auth_response = self._request_new_access_token()
@@ -1,3 +1,4 @@
1
+ import functools
1
2
  import os
2
3
  import platform
3
4
  import ssl
@@ -39,16 +40,29 @@ class SystemStorageSslContext(HTTPAdapter):
39
40
  conn.ca_certs = None
40
41
 
41
42
 
43
+ @functools.cache
44
+ def _get_session() -> requests.Session:
45
+ """Process-wide Session so TCP+TLS connections are reused across all API calls."""
46
+ session = requests.Session()
47
+ # On Windows without an explicit CA bundle env var, fall back to the system
48
+ # trust store via a custom SSL context.
49
+ if platform.system() == 'Windows' and not (
50
+ os.environ.get('REQUESTS_CA_BUNDLE') or os.environ.get('CURL_CA_BUNDLE')
51
+ ):
52
+ session.mount('https://', SystemStorageSslContext())
53
+ return session
54
+
55
+
42
56
  def _get_request_function() -> Callable:
43
- if os.environ.get('REQUESTS_CA_BUNDLE') or os.environ.get('CURL_CA_BUNDLE'):
44
- return requests.request
57
+ return _get_session().request
45
58
 
46
- if platform.system() != 'Windows':
47
- return requests.request
48
59
 
49
- session = requests.Session()
50
- session.mount('https://', SystemStorageSslContext())
51
- return session.request
60
+ def _log_response(response: Response, url: str, hide_response_content_log: bool) -> None:
61
+ content = 'HIDDEN' if hide_response_content_log else response.text
62
+ logger.debug(
63
+ 'Receiving response, %s',
64
+ {'status_code': response.status_code, 'url': url, 'content': content},
65
+ )
52
66
 
53
67
 
54
68
  _REQUEST_ERRORS_TO_RETRY = (
@@ -182,12 +196,7 @@ class CycodeClientBase:
182
196
  response = _get_request_function()(
183
197
  method='post', url=url, data=tracker, headers=headers, timeout=self.timeout
184
198
  )
185
-
186
- content = 'HIDDEN' if hide_response_content_log else response.text
187
- logger.debug(
188
- 'Receiving response, %s',
189
- {'status_code': response.status_code, 'url': url, 'content': content},
190
- )
199
+ _log_response(response, url, hide_response_content_log)
191
200
 
192
201
  response.raise_for_status()
193
202
  return response
@@ -231,14 +240,8 @@ class CycodeClientBase:
231
240
 
232
241
  try:
233
242
  headers = self.get_request_headers(headers, without_auth=without_auth)
234
- request = _get_request_function()
235
- response = request(method=method, url=url, timeout=timeout, headers=headers, **kwargs)
236
-
237
- content = 'HIDDEN' if hide_response_content_log else response.text
238
- logger.debug(
239
- 'Receiving response, %s',
240
- {'status_code': response.status_code, 'url': url, 'content': content},
241
- )
243
+ response = _get_request_function()(method=method, url=url, timeout=timeout, headers=headers, **kwargs)
244
+ _log_response(response, url, hide_response_content_log)
242
245
 
243
246
  response.raise_for_status()
244
247
  return response
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cycode
3
- Version: 3.16.1.dev7
3
+ Version: 3.16.1.dev8
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
@@ -1,7 +1,7 @@
1
- cycode/__init__.py,sha256=5B0uQpQ89sImWJL007hvzWjchwgJBFIKiQtxtmYyQ6Y,115
1
+ cycode/__init__.py,sha256=siI5hylQu6psc8BhJ8mJVZy2tus_WWs4FzFOEgJbEqg,396
2
2
  cycode/__main__.py,sha256=Z3bD5yrA7yPvAChcADQrqCaZd0ChGI1gdiwALwbWJ6U,104
3
3
  cycode/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- cycode/cli/app.py,sha256=7ReEcVkRX9IaQ2I7jAj7Sl9smbtvxiuK8-9bitMEQik,7491
4
+ cycode/cli/app.py,sha256=AlR2durAEbsa47PDfIj7JtMvJDWA_Dq6wPtVuMJYSCs,10250
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
@@ -64,7 +64,7 @@ cycode/cli/apps/report_import/sbom/sbom_command.py,sha256=uWvBhVdROHcHsjoR3l44h3
64
64
  cycode/cli/apps/sca_options.py,sha256=-3iXoJV5qOkfjr-WGIWuAgaeNYeItJIbm2n6O2Kg5D8,1666
65
65
  cycode/cli/apps/scan/__init__.py,sha256=-q1AIBnrQ4GP0CVKFLr_2CdWf9TBQC90ejSL4I7rxuA,2444
66
66
  cycode/cli/apps/scan/aggregation_report.py,sha256=8f9kPfO7biNf5OsDZG6UhMPqG6ymoFrX5GBtlEIfFAg,1540
67
- cycode/cli/apps/scan/code_scanner.py,sha256=IoU3dAFSXa4o0W8FggTUrgEonI0OYqREuG34i0XBumI,16865
67
+ cycode/cli/apps/scan/code_scanner.py,sha256=jjUzi2yueS0TrW4lo_vkkz1KMoBJfwjCCsKeSmoPW2A,17099
68
68
  cycode/cli/apps/scan/commit_history/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
69
  cycode/cli/apps/scan/commit_history/commit_history_command.py,sha256=zTVmN8yeLXGAiCbyDL-EyEzSPNLzcRpP2q6Qq7p4uZA,1011
70
70
  cycode/cli/apps/scan/commit_range_scanner.py,sha256=axFs--Xcb3I7D_SJHtBNE810qyCsSzLqPn_rD_8Peb8,17172
@@ -84,8 +84,8 @@ cycode/cli/apps/scan/scan_ci/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
84
84
  cycode/cli/apps/scan/scan_ci/ci_integrations.py,sha256=3ZUv1uLsHC13KTNQ4erQKKDXAkmaSm5jow2Utwr4mCw,1634
85
85
  cycode/cli/apps/scan/scan_ci/scan_ci_command.py,sha256=37I6YTs5UWYtbnDe1EeYZnhV1twFTDUrniZ4Sf2_6Kk,562
86
86
  cycode/cli/apps/scan/scan_command.py,sha256=n3bzGjB5CSmLRfFn6-pLBhrlOCDTRz2Kfn2L_J1l0io,7916
87
- cycode/cli/apps/scan/scan_parameters.py,sha256=66Ft8c_L6_BxDvRgJoXP5ItUQfzSHGF_XJWBdQismrg,1341
88
- cycode/cli/apps/scan/scan_result.py,sha256=mIxALi_qUfXHoauSU7SknX0xLp8P9WdhV3oFc8jp_SA,8497
87
+ cycode/cli/apps/scan/scan_parameters.py,sha256=x0UPwLQx85SW-pdPG6bNkChmnBXhWA8lnUCdY08urq8,1409
88
+ cycode/cli/apps/scan/scan_result.py,sha256=hTPB2wnsbqSNqAxhWN4P7_ygDN_j5Mufs_shIb0FJU4,8624
89
89
  cycode/cli/apps/status/__init__.py,sha256=uxfkEBafO7Da0mPc1fZhwoO0RTtyXp2a5T3LJTZxubU,371
90
90
  cycode/cli/apps/status/get_cli_status.py,sha256=1UAVyKPpYk2S8nQYshKD1x5xgWR-hCTchLChxNU0JVA,2577
91
91
  cycode/cli/apps/status/models.py,sha256=2SBpJlh_MNCPxv8aXMV5D4GfK6-G-XB0GlMFZ3Nep_o,1907
@@ -189,13 +189,13 @@ cycode/cyclient/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
189
189
  cycode/cyclient/ai_security_manager_client.py,sha256=aZKsq2yB7ZiptMGj0RBTuPYyjYiJ5FTjNTi5_i37t4Q,4282
190
190
  cycode/cyclient/ai_security_manager_service_config.py,sha256=83pQzgOb93JW6E-dznJkI4c0NEXmQRlx9YZKMmjVwp8,808
191
191
  cycode/cyclient/auth_client.py,sha256=TwbmZ358Ancf-Q-IZolvfljZ8691_6botsqd0R0PLPk,2105
192
- cycode/cyclient/base_token_auth_client.py,sha256=3JIrSz0-ywVTIfxIs2zs5aGcE-x5GW3AgPHm9qA4ZDE,3857
192
+ cycode/cyclient/base_token_auth_client.py,sha256=mn5580d7A8Z2_zcdFKIJk78ADK7mViwTcV-4QCpRCGo,4369
193
193
  cycode/cyclient/cli_activation_client.py,sha256=QaATFVABf0vQ6cTU_dCN9UT6fUkJf5qciaK8W7gQDrc,419
194
194
  cycode/cyclient/client_creator.py,sha256=-bsOT7M28yx-WT1UhSzENY8N9EgoQIv48Smrr9izQTI,2849
195
195
  cycode/cyclient/config.py,sha256=Le3YYp7LyclTwS4vonuFipfRA1qhyC28_muUtxpnImc,1387
196
196
  cycode/cyclient/config_dev.py,sha256=GJ3w8Q-ow5SvXGBFA34eaeoI6GhitavAGy3UfcUh9OU,120
197
197
  cycode/cyclient/cycode_client.py,sha256=ifZMA4RlFw4QNHku5ZxmtUKglH2yd1479yYZGDtxVvw,257
198
- cycode/cyclient/cycode_client_base.py,sha256=N9awOsu0nP7yuRbYwFNbkhR99L2Z84k7sC-wiFaC8Pw,10018
198
+ cycode/cyclient/cycode_client_base.py,sha256=_53DmKG_RAEY9INP_27s9FPa4_StQI5IgWt1Pl_Coq8,10193
199
199
  cycode/cyclient/cycode_dev_based_client.py,sha256=8LxeUWizXzZ0ilpwb6Q0W4ZMLZyZdKPzgpl5xcRmT8c,664
200
200
  cycode/cyclient/cycode_oidc_based_client.py,sha256=AVKBlqFOLYUQVxyPquvFwqnYpD6xgU_R6GJiQCWEdJw,857
201
201
  cycode/cyclient/cycode_token_based_client.py,sha256=frbrv1jzF388SXqHNNkZ95Hbx7Vjd3UXwWnq7nVxYN8,848
@@ -207,8 +207,8 @@ cycode/cyclient/report_client.py,sha256=Scq30NeJPzgXv0hPLO1U05AdE9i_2iu6cIrSKpEJ
207
207
  cycode/cyclient/scan_client.py,sha256=6TK5FQkfrvV7PHqRnUzEn1PBNd2oPYVamvIixcUfe3c,16755
208
208
  cycode/cyclient/scan_config_base.py,sha256=mXsPZGYCtp85rv5GIige40yQZXuRcEKUW-VQJ0vgFzk,1201
209
209
  cycode/logger.py,sha256=EfZGRK6VC5rE_LAjIcRrHFiQCueylCDXoG6bvGkrIME,2111
210
- cycode-3.16.1.dev7.dist-info/METADATA,sha256=Y43jx9fdiLPcKksSYbryGOKCx5bTtFWzTYbmb5Qn5HI,89245
211
- cycode-3.16.1.dev7.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
212
- cycode-3.16.1.dev7.dist-info/entry_points.txt,sha256=iDcVJM8ByLElVgvBgtYxDjw1kT7O8Mo0LcWZIT5L3Ig,45
213
- cycode-3.16.1.dev7.dist-info/licenses/LICENCE,sha256=2Wx4N6mD_4xB7-E3hPkZ3MPhpJy__k_I8MaCSO-PDRo,1068
214
- cycode-3.16.1.dev7.dist-info/RECORD,,
210
+ cycode-3.16.1.dev8.dist-info/METADATA,sha256=KBETRmc3w4f-wgb_zt3RtCIhHKLI5aa_Vz_axKwCFK4,89245
211
+ cycode-3.16.1.dev8.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
212
+ cycode-3.16.1.dev8.dist-info/entry_points.txt,sha256=iDcVJM8ByLElVgvBgtYxDjw1kT7O8Mo0LcWZIT5L3Ig,45
213
+ cycode-3.16.1.dev8.dist-info/licenses/LICENCE,sha256=2Wx4N6mD_4xB7-E3hPkZ3MPhpJy__k_I8MaCSO-PDRo,1068
214
+ cycode-3.16.1.dev8.dist-info/RECORD,,