cycode 3.22.1.dev2__py3-none-any.whl → 3.22.1.dev4__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
@@ -5,4 +5,4 @@ import time as _time
5
5
  # end-to-end scan duration from the moment the user actually triggered it.
6
6
  _BOOT_WALL: float = _time.time()
7
7
 
8
- __version__ = '3.22.1.dev2' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
8
+ __version__ = '3.22.1.dev4' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
@@ -127,16 +127,20 @@ def install_hooks(
127
127
  ide: IDE,
128
128
  scope: str = 'user',
129
129
  repo_path: Optional[Path] = None,
130
- report_mode: bool = False,
131
130
  ) -> tuple[bool, str]:
132
131
  """Install Cycode AI guardrails hooks for ``ide``."""
133
132
  hooks_path = ide.settings_path(scope, repo_path)
134
133
 
135
- existing = _load_hooks_file(hooks_path) or {'version': 1, 'hooks': {}}
136
- existing.setdefault('version', 1)
134
+ existing = _load_hooks_file(hooks_path) or {'hooks': {}}
137
135
  existing.setdefault('hooks', {})
138
136
 
139
- rendered = ide.render_hooks_config(async_mode=report_mode)
137
+ rendered = ide.render_hooks_config()
138
+
139
+ # Top-level fields come from the IDE's render only - Codex rejects a hooks file
140
+ # with unknown top-level fields, so no `version` may be injected here.
141
+ for key, value in rendered.items():
142
+ if key != 'hooks':
143
+ existing[key] = value
140
144
 
141
145
  for event, entries in rendered['hooks'].items():
142
146
  existing['hooks'].setdefault(event, [])
@@ -14,7 +14,6 @@ event handlers; `IDE.build_hook_response` translates it into the IDE-specific
14
14
  JSON response shape that the IDE expects on stdout.
15
15
  """
16
16
 
17
- import platform
18
17
  from abc import ABC, abstractmethod
19
18
  from dataclasses import dataclass
20
19
  from enum import Enum
@@ -25,24 +24,6 @@ from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
25
24
  from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType
26
25
 
27
26
 
28
- def shell_background_suffix(async_mode: bool) -> str:
29
- """`' &'` when backgrounding is requested and the platform's shell supports it.
30
-
31
- Only valid for hooks whose runner is stdin-safe under backgrounding (zsh keeps
32
- a backgrounded command's stdin; verified for Cursor/Codex). bash/sh reattach it
33
- to /dev/null, silently emptying the payload — hooks that run under bash (e.g.
34
- Copilot's `bash` field) must add an explicit `<&0` redirect instead.
35
-
36
- Windows gets no suffix: depending on the IDE, hooks may run under cmd (where a
37
- trailing `&` is a no-op separator) or Windows PowerShell (where it's a parse
38
- error that would fail the hook). Until the CLI can self-detach in report mode,
39
- Windows hooks run synchronously.
40
- """
41
- if not async_mode or platform.system() == 'Windows':
42
- return ''
43
- return ' &'
44
-
45
-
46
27
  class DecisionAction(str, Enum):
47
28
  """Canonical decision action returned by event handlers."""
48
29
 
@@ -118,7 +99,7 @@ class IDE(ABC):
118
99
  """
119
100
 
120
101
  @abstractmethod
121
- def render_hooks_config(self, async_mode: bool = False) -> dict:
102
+ def render_hooks_config(self) -> dict:
122
103
  """Return the settings blob to merge into the IDE's settings file.
123
104
 
124
105
  Shape is IDE-specific (Cursor uses a flat ``{event: [{command}]}`` dict;
@@ -249,12 +249,8 @@ class ClaudeCode(IDE):
249
249
  return repo_path / _REPO_SUBDIR / _HOOKS_FILE_NAME
250
250
  return _USER_HOOKS_DIR / _HOOKS_FILE_NAME
251
251
 
252
- def render_hooks_config(self, async_mode: bool = False) -> dict:
253
- # Claude Code uses a nested hook structure with optional async/timeout.
252
+ def render_hooks_config(self) -> dict:
254
253
  hook_entry: dict = {'type': 'command', 'command': _SCAN_COMMAND}
255
- if async_mode:
256
- hook_entry['async'] = True
257
- hook_entry['timeout'] = 20
258
254
 
259
255
  return {
260
256
  'hooks': {
@@ -20,7 +20,7 @@ from cycode.cli.apps.ai_guardrails.ides._plugin_utils import (
20
20
  resolve_cached_plugin_dir,
21
21
  walk_enabled_plugins,
22
22
  )
23
- from cycode.cli.apps.ai_guardrails.ides.base import IDE, DecisionAction, HookDecision, shell_background_suffix
23
+ from cycode.cli.apps.ai_guardrails.ides.base import IDE, DecisionAction, HookDecision
24
24
  from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
25
25
  from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType
26
26
  from cycode.cli.utils.jwt_utils import decode_jwt_unverified
@@ -189,11 +189,10 @@ class Codex(IDE):
189
189
  return repo_path / _CONFIG_DIR_NAME / _HOOKS_FILE_NAME
190
190
  return _codex_home() / _HOOKS_FILE_NAME
191
191
 
192
- def render_hooks_config(self, async_mode: bool = False) -> dict:
193
- # Codex's TOML `async: true` flag is unimplemented; shell-background via
194
- # `&` is the working mechanism (unix only). SessionStart stays sync so
195
- # the conversation context is registered before any scan hook fires.
196
- scan_cmd = f'{_SCAN_COMMAND}{shell_background_suffix(async_mode)}'
192
+ def render_hooks_config(self) -> dict:
193
+ # SessionStart stays sync so the conversation context is registered
194
+ # before any scan hook fires.
195
+ scan_cmd = _SCAN_COMMAND
197
196
  return {
198
197
  'hooks': {
199
198
  'SessionStart': [
@@ -379,24 +379,9 @@ class Copilot(IDE):
379
379
  return repo_path / _REPO_HOOKS_SUBDIR / _HOOKS_FILE_NAME
380
380
  return _copilot_home() / 'hooks' / _HOOKS_FILE_NAME
381
381
 
382
- def render_hooks_config(self, async_mode: bool = False) -> dict:
382
+ def render_hooks_config(self) -> dict:
383
+ # Single cross-platform `command` field, copied to both shells by Copilot.
383
384
  def entry(command: str) -> dict:
384
- if async_mode:
385
- # Copilot has no async hook flag; background via shell on unix. Both
386
- # redirects are load-bearing. `<&0` keeps the payload flowing: a bare
387
- # `cmd &` gets its stdin reattached to /dev/null by the shell (job
388
- # control is off in hooks), so the scan reads nothing and allows. The
389
- # stdout redirect is what actually makes it async: the backgrounded
390
- # child inherits the hook's stdout and the runner waits on that pipe
391
- # for EOF, so without it the scan blocks the response it was meant to
392
- # run behind. Windows PowerShell has no trailing-&, so it stays sync.
393
- return {
394
- 'type': 'command',
395
- 'bash': f'{command} <&0 >/dev/null 2>&1 &',
396
- 'powershell': command,
397
- 'timeoutSec': _HOOK_TIMEOUT_SEC,
398
- }
399
- # Single cross-platform `command` field, copied to both shells by Copilot.
400
385
  return {'type': 'command', 'command': command, 'timeoutSec': _HOOK_TIMEOUT_SEC}
401
386
 
402
387
  return {
@@ -7,7 +7,7 @@ from typing import ClassVar, Optional
7
7
 
8
8
  from cycode.cli.apps.ai_guardrails.consts import CYCODE_SCAN_PROMPT_COMMAND, CYCODE_SESSION_START_COMMAND
9
9
  from cycode.cli.apps.ai_guardrails.ides._plugin_utils import build_global_config_file
10
- from cycode.cli.apps.ai_guardrails.ides.base import IDE, DecisionAction, HookDecision, shell_background_suffix
10
+ from cycode.cli.apps.ai_guardrails.ides.base import IDE, DecisionAction, HookDecision
11
11
  from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
12
12
  from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType
13
13
  from cycode.logger import get_logger
@@ -68,9 +68,8 @@ class Cursor(IDE):
68
68
  return repo_path / _REPO_SUBDIR / _HOOKS_FILE_NAME
69
69
  return _user_hooks_dir() / _HOOKS_FILE_NAME
70
70
 
71
- def render_hooks_config(self, async_mode: bool = False) -> dict:
72
- command = f'{_SCAN_COMMAND}{shell_background_suffix(async_mode)}'
73
- hooks = {event: [{'command': command}] for event in self.hook_events}
71
+ def render_hooks_config(self) -> dict:
72
+ hooks = {event: [{'command': _SCAN_COMMAND}] for event in self.hook_events}
74
73
  hooks['sessionStart'] = [{'command': _SESSION_START_COMMAND}]
75
74
  return {'version': 1, 'hooks': hooks}
76
75
 
@@ -65,11 +65,9 @@ def install_command(
65
65
  repo_path = resolve_repo_path(scope, repo_path)
66
66
  ides_to_install = resolve_ides(ide)
67
67
 
68
- report_mode = mode == GuardrailsMode.REPORT
69
-
70
68
  results: list[tuple[str, bool, str]] = []
71
69
  for current_ide in ides_to_install:
72
- success, message = install_hooks(current_ide, scope, repo_path, report_mode=report_mode)
70
+ success, message = install_hooks(current_ide, scope, repo_path)
73
71
  results.append((current_ide.display_name, success, message))
74
72
 
75
73
  any_success = False
@@ -108,6 +106,6 @@ def _print_next_steps(results: list[tuple[str, bool, str]], mode: GuardrailsMode
108
106
  console.print('2. (Optional) Customize policy in ~/.cycode/ai-guardrails.yaml')
109
107
  console.print()
110
108
  if mode == GuardrailsMode.REPORT:
111
- console.print('[dim]Report mode: hooks run async (non-blocking) and policy is set to warn.[/]')
109
+ console.print('[dim]Report mode: policy is set to warn.[/]')
112
110
  else:
113
111
  console.print('[dim]The hooks will scan prompts, file reads, and MCP tool calls for secrets.[/]')
@@ -0,0 +1,71 @@
1
+ """Self-detach support for report-mode scans.
2
+
3
+ In report mode nobody consumes the hook verdict, so the scan respawns itself
4
+ detached and the parent exits immediately — the IDE is released after roughly
5
+ CLI startup instead of waiting for a full scan. The child's handles are set at
6
+ process-creation time (stdout/stderr to devnull, payload piped to stdin), so it
7
+ never inherits the IDE's pipes: a backgrounded child that shares the hook's
8
+ stdout keeps EOF-waiting runners blocked for the scan's full duration.
9
+ """
10
+
11
+ import os
12
+ import subprocess
13
+ import sys
14
+
15
+ from cycode.logger import get_logger
16
+
17
+ logger = get_logger('AI Guardrails')
18
+
19
+ DETACHED_ENV_VAR = '_CYCODE_DETACHED'
20
+
21
+ # Numeric fallbacks let non-Windows platforms (tests included) build the same flags.
22
+ _WINDOWS_CREATIONFLAGS = (
23
+ getattr(subprocess, 'DETACHED_PROCESS', 0x00000008)
24
+ | getattr(subprocess, 'CREATE_NEW_PROCESS_GROUP', 0x00000200)
25
+ | getattr(subprocess, 'CREATE_NO_WINDOW', 0x08000000)
26
+ )
27
+
28
+
29
+ def is_detached_child() -> bool:
30
+ """Whether this process is the respawned detached child."""
31
+ return os.environ.get(DETACHED_ENV_VAR) == '1'
32
+
33
+
34
+ def build_respawn_command() -> list[str]:
35
+ """The command that re-runs the current invocation.
36
+
37
+ Under PyInstaller sys.executable is the CLI binary itself; otherwise it is
38
+ the Python interpreter and argv[0] is the console script to re-run.
39
+ """
40
+ if getattr(sys, 'frozen', False):
41
+ return [sys.executable, *sys.argv[1:]]
42
+ return [sys.executable, *sys.argv]
43
+
44
+
45
+ def respawn_detached(stdin_payload: str) -> bool:
46
+ """Respawn the current command detached, feeding it ``stdin_payload``.
47
+
48
+ Returns False when the respawn failed, so the caller can fall back to the
49
+ synchronous path instead of dropping the event.
50
+ """
51
+ try:
52
+ detach_kwargs = (
53
+ {'creationflags': _WINDOWS_CREATIONFLAGS} if sys.platform == 'win32' else {'start_new_session': True}
54
+ )
55
+ process = subprocess.Popen( # noqa: S603
56
+ build_respawn_command(),
57
+ stdin=subprocess.PIPE,
58
+ stdout=subprocess.DEVNULL,
59
+ stderr=subprocess.DEVNULL,
60
+ env={**os.environ, DETACHED_ENV_VAR: '1'},
61
+ **detach_kwargs,
62
+ )
63
+ # Hand over the payload and close, then return without waiting - the
64
+ # whole point is that the parent exits while the child scans.
65
+ process.stdin.write(stdin_payload.encode('utf-8'))
66
+ process.stdin.close()
67
+ logger.debug('Respawned detached scan', extra={'child_pid': process.pid})
68
+ return True
69
+ except Exception as e:
70
+ logger.debug('Failed to respawn detached, falling back to synchronous scan', exc_info=e)
71
+ return False
@@ -318,6 +318,30 @@ def get_effective_mode(policy: dict, feature_config: dict) -> GuardrailsMode:
318
318
  return GuardrailsMode.BLOCK if (mode == PolicyMode.BLOCK and action == PolicyMode.BLOCK) else GuardrailsMode.REPORT
319
319
 
320
320
 
321
+ # The policy section each event's handler reads its feature config from.
322
+ _FEATURE_KEY_BY_EVENT_TYPE: dict[str, str] = {
323
+ AiHookEventType.PROMPT.value: 'prompt',
324
+ AiHookEventType.FILE_READ.value: 'file_read',
325
+ AiHookEventType.MCP_EXECUTION.value: 'mcp',
326
+ }
327
+
328
+
329
+ def should_detach_scan(policy: dict, event_name: str) -> bool:
330
+ """Whether this event's scan is safe to run detached.
331
+
332
+ Report mode never blocks, so nobody consumes the verdict. Fail-closed
333
+ configs stay synchronous even in report mode: their deny on scan failure
334
+ must reach the IDE. Unknown events stay synchronous - they exit fast anyway.
335
+ """
336
+ feature_key = _FEATURE_KEY_BY_EVENT_TYPE.get(event_name)
337
+ if feature_key is None:
338
+ return False
339
+ if not get_policy_value(policy, 'fail_open', default=True):
340
+ return False
341
+ feature_config = get_policy_value(policy, feature_key, default={})
342
+ return get_effective_mode(policy, feature_config) == GuardrailsMode.REPORT
343
+
344
+
321
345
  def build_ai_guardrails_scan_parameters(
322
346
  ctx: typer.Context,
323
347
  paths: Optional[tuple[str, ...]],
@@ -15,7 +15,8 @@ import typer
15
15
 
16
16
  from cycode.cli.apps.ai_guardrails.ides import DEFAULT_IDE_NAME, get_ide
17
17
  from cycode.cli.apps.ai_guardrails.ides.base import HookDecision
18
- from cycode.cli.apps.ai_guardrails.scan.handlers import get_handler_for_event
18
+ from cycode.cli.apps.ai_guardrails.scan.detach import is_detached_child, respawn_detached
19
+ from cycode.cli.apps.ai_guardrails.scan.handlers import get_handler_for_event, should_detach_scan
19
20
  from cycode.cli.apps.ai_guardrails.scan.policy import load_policy
20
21
  from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType
21
22
  from cycode.cli.apps.ai_guardrails.scan.utils import output_json, read_stdin_text, safe_json_parse
@@ -139,6 +140,12 @@ def scan_command(
139
140
  workspace_roots = payload.get('workspace_roots') or ['.']
140
141
  policy = load_policy(workspace_roots[0])
141
142
 
143
+ # Report mode: nobody consumes the verdict, so hand the scan to a detached
144
+ # child and release the IDE immediately. Runs before any client or network
145
+ # work. A failed respawn falls through to the synchronous path.
146
+ if not is_detached_child() and should_detach_scan(policy, event_name) and respawn_detached(stdin_data):
147
+ return
148
+
142
149
  try:
143
150
  _initialize_clients(ctx)
144
151
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cycode
3
- Version: 3.22.1.dev2
3
+ Version: 3.22.1.dev4
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,4 +1,4 @@
1
- cycode/__init__.py,sha256=kbcFQ8sxYmyOwjOJhzZTd-BC3xoxSgYPIyTUckewgmw,396
1
+ cycode/__init__.py,sha256=0OYQ73ic4xWKSKdUYEGfnvcdRSma4BwfGeDBY8YmEzE,396
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=AlR2durAEbsa47PDfIj7JtMvJDWA_Dq6wPtVuMJYSCs,10250
@@ -7,21 +7,22 @@ cycode/cli/apps/activation_manager.py,sha256=Hz9PDJFB-ZmYi4HSG8iYC-fR8j5v25VuUU-
7
7
  cycode/cli/apps/ai_guardrails/__init__.py,sha256=NsqB1Ca83BIjJMcDSt6suec6Ed0iNnacC0gBqkuuTtI,1367
8
8
  cycode/cli/apps/ai_guardrails/command_utils.py,sha256=NVwd0-2RGRKIqhsQ-4LNDR1D0gVm_o7n-z5LxG2bqAo,800
9
9
  cycode/cli/apps/ai_guardrails/consts.py,sha256=U9lToL22ik3oX6bcZxT5de79Fu0eZA5D5p-q9daE5Iw,778
10
- cycode/cli/apps/ai_guardrails/hooks_manager.py,sha256=_8EjjpaDoeUrlh6gDXechWyev0LIGEZgcA9YJBl21xw,9314
10
+ cycode/cli/apps/ai_guardrails/hooks_manager.py,sha256=FSZYYCzPDM86cUaJO6QHM_1e2yDZZcHa-y97qDT3MjY,9470
11
11
  cycode/cli/apps/ai_guardrails/ides/__init__.py,sha256=9FPz984poZWPX6S6abuo-PThiXgJ_WpoUv47wiSXnKQ,2639
12
12
  cycode/cli/apps/ai_guardrails/ides/_plugin_utils.py,sha256=XPIc9pZFEgdGVTSSAl9yqI48F88GKlFnJ5FsLUhHjCE,3981
13
- cycode/cli/apps/ai_guardrails/ides/base.py,sha256=RZintDpwWgl0l1cBVKccG4ek1jkTMuzRJ-hq91V5ehc,8224
14
- cycode/cli/apps/ai_guardrails/ides/claude_code.py,sha256=eMEL1vjcwGeFZGHqTrnjUBWVn1RHlEyTzrhGPYvuCSw,14977
15
- cycode/cli/apps/ai_guardrails/ides/codex.py,sha256=1nI0TPeCgg2zI1PCVmLi0GouD8HGwGoa6RsIshDDa9c,11972
16
- cycode/cli/apps/ai_guardrails/ides/copilot.py,sha256=LcSbZ_5nENYJqxnFJwUlXFQZiHZqxBIjNjin8gIAL_k,22285
17
- cycode/cli/apps/ai_guardrails/ides/cursor.py,sha256=-Jr76dD8m2xwvb0i93LtwjaloB_oon0OjBUPkv1K6f0,5633
18
- cycode/cli/apps/ai_guardrails/install_command.py,sha256=faNM-5SPpuaKA-9kQ2IdOq9jxJipRfykielgbHk5l-k,4299
13
+ cycode/cli/apps/ai_guardrails/ides/base.py,sha256=gyCxeDxuXaRBpYFzbgKHJAyJ6a1cDcXCGA3FKh67soM,7333
14
+ cycode/cli/apps/ai_guardrails/ides/claude_code.py,sha256=r15sdxksQw5lyvn90HV7sFoxh9p_XAxIO9XY9I_TJKs,14770
15
+ cycode/cli/apps/ai_guardrails/ides/codex.py,sha256=eHdd2_F5JB74RJV2b35oIxRT2Bc6o46ypzfRz26_HZg,11756
16
+ cycode/cli/apps/ai_guardrails/ides/copilot.py,sha256=oMJs54TES_7Q1cnFjxEO4a_qp8-AZ6Janspi5Wjs5K0,21305
17
+ cycode/cli/apps/ai_guardrails/ides/cursor.py,sha256=nZjarVu_zju05_R-1smWtq7U40axk6XnCmzWA-aCZqI,5514
18
+ cycode/cli/apps/ai_guardrails/install_command.py,sha256=3IV_MHpc7R-OJ2kUTDAtemkGZUQ9YAWX3lJBgTsi0zo,4190
19
19
  cycode/cli/apps/ai_guardrails/scan/__init__.py,sha256=qJc82XiQGiAuc1sYY8Ij_A-qXpxgLPuayQq8xWlouMA,48
20
20
  cycode/cli/apps/ai_guardrails/scan/consts.py,sha256=drAslw6vW3kxmbCs2qPCUbUPR7PJouT2lsXtu5sD-lQ,1094
21
- cycode/cli/apps/ai_guardrails/scan/handlers.py,sha256=m0Jpd9AMjuUBj7ry3w_S8sslCiTMv6szBmMzdl3sE_M,17848
21
+ cycode/cli/apps/ai_guardrails/scan/detach.py,sha256=8BRgBq9Qi8nXEx6NKlE8scSRPkO30XCfsiI5PLvq6Gk,2700
22
+ cycode/cli/apps/ai_guardrails/scan/handlers.py,sha256=wxDY2-bxc9MNVWWediQPpm635IiW0WSNaetrZOH39WQ,18823
22
23
  cycode/cli/apps/ai_guardrails/scan/payload.py,sha256=sWsWq5yXP54MVCajsb180kLuprI_M2kspMOzQGh7r_o,1534
23
24
  cycode/cli/apps/ai_guardrails/scan/policy.py,sha256=BZoNNdDQ9tqnfwhB4X1-bDtudaOQc_gXizm3IVwa28o,3351
24
- cycode/cli/apps/ai_guardrails/scan/scan_command.py,sha256=Hs3vkYJztufulFci2N0RNSU6L5UzPMeepts4YgOYggQ,7005
25
+ cycode/cli/apps/ai_guardrails/scan/scan_command.py,sha256=YpfGVmP05qjeoCr_6UVW3rO_MAJ6CJZDfrR1v2OJZ8Y,7465
25
26
  cycode/cli/apps/ai_guardrails/scan/types.py,sha256=ybQm242QN0l_4SSNX4xMHXxzqEK-MW-hfIOixI7zvGU,1497
26
27
  cycode/cli/apps/ai_guardrails/scan/utils.py,sha256=QzR_zmivDYwg2-F8g4bFfsycHhNo-pPuJly0P_l0gm8,2879
27
28
  cycode/cli/apps/ai_guardrails/session_start_command.py,sha256=05Li-ON2U1BU_8CR2YYw1y5HVvoWPTf19ne--EgPqmQ,5921
@@ -214,8 +215,8 @@ cycode/cyclient/report_client.py,sha256=Scq30NeJPzgXv0hPLO1U05AdE9i_2iu6cIrSKpEJ
214
215
  cycode/cyclient/scan_client.py,sha256=DqAZ7u6Z_cvw9A9RlLkAQUgLRwPCCAsUq5U9umt4F7Y,16955
215
216
  cycode/cyclient/scan_config_base.py,sha256=mXsPZGYCtp85rv5GIige40yQZXuRcEKUW-VQJ0vgFzk,1201
216
217
  cycode/logger.py,sha256=EfZGRK6VC5rE_LAjIcRrHFiQCueylCDXoG6bvGkrIME,2111
217
- cycode-3.22.1.dev2.dist-info/METADATA,sha256=LxMBK7Nl1E58XEOWRgVNwe1NC0lDfZki9wam52yTsBk,93596
218
- cycode-3.22.1.dev2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
219
- cycode-3.22.1.dev2.dist-info/entry_points.txt,sha256=iDcVJM8ByLElVgvBgtYxDjw1kT7O8Mo0LcWZIT5L3Ig,45
220
- cycode-3.22.1.dev2.dist-info/licenses/LICENCE,sha256=2Wx4N6mD_4xB7-E3hPkZ3MPhpJy__k_I8MaCSO-PDRo,1068
221
- cycode-3.22.1.dev2.dist-info/RECORD,,
218
+ cycode-3.22.1.dev4.dist-info/METADATA,sha256=OIb4pKloaaIir8rUBb_SCnS8EP5zonGAIYdi0hd35FU,93596
219
+ cycode-3.22.1.dev4.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
220
+ cycode-3.22.1.dev4.dist-info/entry_points.txt,sha256=iDcVJM8ByLElVgvBgtYxDjw1kT7O8Mo0LcWZIT5L3Ig,45
221
+ cycode-3.22.1.dev4.dist-info/licenses/LICENCE,sha256=2Wx4N6mD_4xB7-E3hPkZ3MPhpJy__k_I8MaCSO-PDRo,1068
222
+ cycode-3.22.1.dev4.dist-info/RECORD,,