cycode 3.19.1.dev1__py3-none-any.whl → 3.19.2.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.
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.19.1.dev1' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
8
+ __version__ = '3.19.2.dev1' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
@@ -282,10 +282,8 @@ class ClaudeCode(IDE):
282
282
  }
283
283
 
284
284
  def matches_payload(self, raw_payload: dict) -> bool:
285
- # transcript_path is a documented Claude Code common field, present on every
286
- # hook event. VS Code Copilot emits near-identical payloads (same event names,
287
- # snake_case fields) without it — requiring it keeps those from being
288
- # processed as Claude Code events.
285
+ # transcript_path is a documented Claude Code field, present on every hook event.
286
+ # Positive test by design: an absence check breaks silently when a vendor adds a field.
289
287
  return raw_payload.get('hook_event_name', '') in _CLAUDE_CODE_EVENT_NAMES and 'transcript_path' in raw_payload
290
288
 
291
289
  def is_synthetic_prompt(self, raw_payload: dict) -> bool:
@@ -1,16 +1,24 @@
1
- """GitHub Copilot (VS Code extension) integration for AI guardrails.
1
+ """GitHub Copilot integration for AI guardrails.
2
2
 
3
3
  Hooks are installed in Copilot's native format to ``~/.copilot/hooks/cycode.json``
4
- (user scope) or ``<repo>/.github/hooks/cycode.json`` (repo scope). Both locations
5
- are also read by Copilot CLI and the Copilot cloud coding agent, but only the
6
- VS Code payload dialect is parsed here CLI payloads (camelCase, no event name)
7
- are rejected by ``matches_payload`` and fall through to the allow-and-skip path.
8
-
9
- VS Code sends Claude-style payloads (``hook_event_name``, ``tool_name``,
10
- ``tool_input``) with structural differences that ``matches_payload`` keys on:
11
- a top-level ISO ``timestamp`` and no ``transcript_path``. Copilot hooks have no
12
- matchers, so ``preToolUse`` fires for every tool; tools we don't scan pass
13
- through as raw event names, which match no handler and allow immediately.
4
+ (user scope) or ``<repo>/.github/hooks/cycode.json`` (repo scope). One file, but
5
+ two runtimes are known to execute it: VS Code's own chat runtime, and the Copilot
6
+ agent runtime (Copilot CLI, and VS Code agent sessions). The repo-scope location
7
+ is also read by the Copilot cloud coding agent, whose dialect is untested here.
8
+
9
+ Both deliver Claude-style payloads (``hook_event_name``, ``tool_name``,
10
+ ``tool_input``) when the event keys are registered in PascalCase; the agent runtime
11
+ answers camelCase keys with its own dialect (``sessionId``, no event name) instead.
12
+ Copilot payloads are told apart from Claude Code's by the one field Claude Code
13
+ never sends, a top-level ``timestamp``; ``transcript_path`` cannot discriminate,
14
+ since VS Code sends one of its own whenever a folder is open.
15
+
16
+ The tool vocabulary still differs by runtime — VS Code reads files with
17
+ ``read_file``/``filePath`` and names MCP tools ``mcp_<server>_<tool>``, the agent
18
+ runtime uses ``Read``/``path`` and ``<server>-<tool>`` — so both are accepted.
19
+ Copilot hooks have no matchers, so ``PreToolUse`` fires for every tool; tools we
20
+ don't scan pass through as raw event names, which match no handler and allow
21
+ immediately.
14
22
  """
15
23
 
16
24
  import json
@@ -36,20 +44,36 @@ from cycode.logger import get_logger
36
44
 
37
45
  logger = get_logger('AI Guardrails Copilot')
38
46
 
39
- # Payload dialect (VS Code sends Claude-style PascalCase event names).
47
+ # Payload dialect (Claude-style PascalCase event names).
40
48
  _COPILOT_SCAN_EVENT_NAMES = frozenset({'UserPromptSubmit', 'PreToolUse'})
41
- _READ_FILE_TOOL = 'read_file'
42
- # VS Code names MCP tools `mcp_<server>_<tool>` (single underscores).
49
+
50
+ # Two tool vocabularies reach us through one hooks file: VS Code's own runtime
51
+ # names file reads `read_file` with a `filePath` argument, while the Copilot agent
52
+ # runtime (Copilot CLI, and VS Code agent sessions) names them `Read` with `path`.
53
+ # The names are disjoint, so both are accepted rather than switched between.
54
+ _READ_FILE_TOOLS = frozenset({'read_file', 'Read'})
55
+ _READ_PATH_KEYS = ('path', 'filePath')
56
+
57
+ # VS Code names MCP tools `mcp_<server>_<tool>` (single underscores); the agent
58
+ # runtime uses `<server>-<tool>` with no prefix (its SDK documents that wire form),
59
+ # leaving a hyphen as the only marker of an MCP call there. Every built-in agent
60
+ # tool observed is lower snake_case (`view`, `glob`, `str_replace`, `ask_user`) or
61
+ # PascalCase (`Read`), so this holds for them — but SDK- or custom-agent-registered
62
+ # tools may be named freely. A hyphenated custom tool would be scanned as an MCP
63
+ # call with no resolvable server: an extra scan, never a missed one, which is the
64
+ # safe direction to err for a guardrail.
43
65
  _MCP_TOOL_PREFIX = 'mcp_'
66
+ _MCP_AGENT_SEPARATOR = '-'
44
67
 
45
- # Hooks-file dialect (Copilot-native camelCase event names).
46
- _HOOK_EVENTS = ['userPromptSubmitted', 'preToolUse']
68
+ # Hooks-file event keys. Their case selects the agent runtime's payload dialect.
69
+ _HOOK_EVENTS = ['UserPromptSubmit', 'PreToolUse']
47
70
 
48
71
  _COPILOT_HOME_ENV_VAR = 'COPILOT_HOME'
49
72
  _HOOKS_FILE_NAME = 'cycode.json'
50
73
  _REPO_HOOKS_SUBDIR = Path('.github') / 'hooks'
51
74
  _HOOK_TIMEOUT_SEC = 20
52
75
  _MCP_CONFIG_FILENAME = 'mcp.json'
76
+ _AGENT_MCP_CONFIG_FILENAME = 'mcp-config.json'
53
77
 
54
78
  # Plugin sources. CLI installs register in ~/.copilot/config.json and auto-surface
55
79
  # in VS Code; VS Code UI installs register in ~/.vscode/agent-plugins/installed.json;
@@ -67,13 +91,9 @@ _PLUGIN_MANIFEST_LOCATIONS = (
67
91
  Path('.claude-plugin') / 'plugin.json',
68
92
  )
69
93
 
70
- # --event is ignored by the VS Code payload parsing (the payload self-describes)
71
- # but Copilot CLI payloads carry no event name at all — baking the flag in now
72
- # means CLI support won't require customers to re-install hooks. Values use the
73
- # payload-dialect spelling so a future CLI path can inject them straight into
74
- # hook_event_name and reuse the existing parsing.
75
- _SCAN_PROMPT_COMMAND = f'{CYCODE_SCAN_PROMPT_COMMAND} --ide copilot --event UserPromptSubmit'
76
- _SCAN_TOOL_COMMAND = f'{CYCODE_SCAN_PROMPT_COMMAND} --ide copilot --event PreToolUse'
94
+ # One command for both events: every runtime self-describes via hook_event_name once
95
+ # the events are registered in PascalCase, so --event is no longer passed.
96
+ _SCAN_COMMAND = f'{CYCODE_SCAN_PROMPT_COMMAND} --ide copilot'
77
97
  _SESSION_START_COMMAND = f'{CYCODE_SESSION_START_COMMAND} --ide copilot'
78
98
 
79
99
 
@@ -252,7 +272,11 @@ def _collect_installed_plugins() -> dict:
252
272
 
253
273
 
254
274
  def _known_mcp_server_names() -> list[str]:
255
- """Config-declared MCP server names: user-level ``mcp.json`` + plugin configs.
275
+ """Config-declared MCP server names, across both runtimes' config files.
276
+
277
+ VS Code declares them in its user-level ``mcp.json`` under ``servers``; the
278
+ agent runtime uses ``~/.copilot/mcp-config.json`` under ``mcpServers``. Both are
279
+ read because one hooks file serves both, and plugin configs contribute to either.
256
280
 
257
281
  Best-effort inventory: servers contributed by extensions, ``chat.mcp.discovery``
258
282
  imports, dev containers, or non-default profiles are not discoverable from disk.
@@ -260,6 +284,12 @@ def _known_mcp_server_names() -> list[str]:
260
284
  config = _load_vscode_mcp_config()
261
285
  servers = (config or {}).get('servers')
262
286
  names = list(servers.keys()) if isinstance(servers, dict) else []
287
+
288
+ agent_config = _load_jsonc(_copilot_home() / _AGENT_MCP_CONFIG_FILENAME) or {}
289
+ agent_servers = agent_config.get('mcpServers')
290
+ if isinstance(agent_servers, dict):
291
+ names.extend(agent_servers.keys())
292
+
263
293
  for plugin in _collect_installed_plugins().values():
264
294
  names.extend(plugin.get('mcp_server_names') or [])
265
295
  return names
@@ -278,22 +308,57 @@ def _server_name_variants(server_name: str) -> set[str]:
278
308
  return {v for v in (server_name, underscored, collapsed) if v}
279
309
 
280
310
 
281
- def split_mcp_tool_name(tool_name: str, server_names: Iterable[str]) -> tuple[Optional[str], Optional[str]]:
282
- """Split ``mcp_<server>_<tool>`` into ``(server, tool)``.
311
+ def _read_file_path(tool_name: str, tool_input: object) -> Optional[str]:
312
+ """Path of a file-read tool call, or None when this isn't one.
283
313
 
284
- The ``<server>`` part is VS Code's sanitized (and possibly truncated) form of
285
- the server's SELF-REPORTED handshake name, not the config key so matching
286
- against known config names (and their normalized variants) is best-effort.
287
- When nothing matches, return the unsplit remainder as the tool rather than
288
- fabricating a server from a guessed split.
314
+ The agent runtime reuses its read tool for directory listings, with a payload
315
+ identical to a file read, so the path has to be stat-ed to tell them apart —
316
+ VS Code has no such ambiguity (`read_file` vs `list_dir`). A path that isn't an
317
+ existing file (a directory, or already deleted) has nothing to scan.
289
318
  """
290
- rest = tool_name[len(_MCP_TOOL_PREFIX) :]
319
+ if tool_name not in _READ_FILE_TOOLS or not isinstance(tool_input, dict):
320
+ return None
321
+
322
+ raw_path = next((tool_input[key] for key in _READ_PATH_KEYS if tool_input.get(key)), None)
323
+ if not isinstance(raw_path, str):
324
+ return None
325
+
326
+ try:
327
+ if not Path(raw_path).is_file():
328
+ return None
329
+ except OSError as e:
330
+ logger.debug('Failed to stat read path, %s', {'path': raw_path}, exc_info=e)
331
+ return None
332
+ return raw_path
333
+
334
+
335
+ def is_mcp_tool_name(tool_name: str) -> bool:
336
+ """Whether a tool name is an MCP call in either runtime's naming scheme."""
337
+ return tool_name.startswith(_MCP_TOOL_PREFIX) or _MCP_AGENT_SEPARATOR in tool_name
338
+
339
+
340
+ def split_mcp_tool_name(tool_name: str, server_names: Iterable[str]) -> tuple[Optional[str], Optional[str]]:
341
+ """Split an MCP tool name into ``(server, tool)``.
342
+
343
+ Handles both naming schemes: VS Code's ``mcp_<server>_<tool>`` and the agent
344
+ runtime's prefix-less ``<server>-<tool>``. In the VS Code form the ``<server>``
345
+ part is a sanitized (and possibly truncated) form of the server's SELF-REPORTED
346
+ handshake name rather than the config key, so matching against known config
347
+ names (and their normalized variants) is best-effort. Server names may
348
+ themselves contain the separator, hence the longest-match. When nothing
349
+ matches, return the unsplit remainder as the tool rather than fabricating a
350
+ server from a guessed split.
351
+ """
352
+ if tool_name.startswith(_MCP_TOOL_PREFIX):
353
+ rest, separator = tool_name[len(_MCP_TOOL_PREFIX) :], '_'
354
+ else:
355
+ rest, separator = tool_name, _MCP_AGENT_SEPARATOR
291
356
 
292
357
  best_server = None
293
358
  best_variant_len = -1
294
359
  for server in server_names:
295
360
  for variant in _server_name_variants(server):
296
- if (rest == variant or rest.startswith(f'{variant}_')) and len(variant) > best_variant_len:
361
+ if (rest == variant or rest.startswith(f'{variant}{separator}')) and len(variant) > best_variant_len:
297
362
  best_server = server
298
363
  best_variant_len = len(variant)
299
364
  if best_server is not None:
@@ -317,13 +382,17 @@ class Copilot(IDE):
317
382
  def render_hooks_config(self, async_mode: bool = False) -> dict:
318
383
  def entry(command: str) -> dict:
319
384
  if async_mode:
320
- # Copilot has no async hook flag; background via shell on unix. The
321
- # explicit <&0 keeps the payload flowing: a bare `cmd &` gets its stdin
322
- # reattached to /dev/null by the shell (job control is off in hooks).
323
- # Windows PowerShell has no trailing-& operator, so it stays sync.
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.
324
393
  return {
325
394
  'type': 'command',
326
- 'bash': f'{command} <&0 &',
395
+ 'bash': f'{command} <&0 >/dev/null 2>&1 &',
327
396
  'powershell': command,
328
397
  'timeoutSec': _HOOK_TIMEOUT_SEC,
329
398
  }
@@ -333,42 +402,37 @@ class Copilot(IDE):
333
402
  return {
334
403
  'version': 1,
335
404
  'hooks': {
336
- 'sessionStart': [{'type': 'command', 'command': _SESSION_START_COMMAND}],
337
- 'userPromptSubmitted': [entry(_SCAN_PROMPT_COMMAND)],
338
- 'preToolUse': [entry(_SCAN_TOOL_COMMAND)],
405
+ 'SessionStart': [{'type': 'command', 'command': _SESSION_START_COMMAND}],
406
+ 'UserPromptSubmit': [entry(_SCAN_COMMAND)],
407
+ 'PreToolUse': [entry(_SCAN_COMMAND)],
339
408
  },
340
409
  }
341
410
 
342
411
  def matches_payload(self, raw_payload: dict) -> bool:
343
- # Structural discrimination, no magic strings: VS Code Copilot events carry
344
- # a top-level ISO timestamp and no transcript_path; real Claude Code events
345
- # always carry transcript_path; Copilot CLI payloads have no hook_event_name.
346
- return (
347
- raw_payload.get('hook_event_name', '') in _COPILOT_SCAN_EVENT_NAMES
348
- and 'timestamp' in raw_payload
349
- and 'transcript_path' not in raw_payload
350
- )
412
+ # Structural discrimination, no magic strings: Copilot events carry a top-level
413
+ # timestamp, Claude Code events never do.
414
+ return raw_payload.get('hook_event_name', '') in _COPILOT_SCAN_EVENT_NAMES and 'timestamp' in raw_payload
351
415
 
352
416
  def parse_hook_payload(self, raw_payload: dict) -> AIHookPayload:
353
417
  hook_event_name = raw_payload.get('hook_event_name', '')
354
418
  tool_name = raw_payload.get('tool_name', '')
355
419
  tool_input = raw_payload.get('tool_input')
356
420
 
421
+ read_path = _read_file_path(tool_name, tool_input)
422
+
357
423
  if hook_event_name == 'UserPromptSubmit':
358
424
  canonical_event: Union[AiHookEventType, str] = AiHookEventType.PROMPT
359
- elif hook_event_name == 'PreToolUse' and tool_name == _READ_FILE_TOOL:
425
+ elif hook_event_name == 'PreToolUse' and read_path is not None:
360
426
  canonical_event = AiHookEventType.FILE_READ
361
- elif hook_event_name == 'PreToolUse' and tool_name.startswith(_MCP_TOOL_PREFIX):
427
+ elif hook_event_name == 'PreToolUse' and is_mcp_tool_name(tool_name):
362
428
  canonical_event = AiHookEventType.MCP_EXECUTION
363
429
  else:
364
- # No matchers in Copilot hooks: preToolUse fires for every tool. Pass
430
+ # No matchers in Copilot hooks: PreToolUse fires for every tool. Pass
365
431
  # the raw tool name through — it matches no handler, so scan_command
366
432
  # answers with a neutral allow before any policy/network work.
367
433
  canonical_event = tool_name or hook_event_name
368
434
 
369
- file_path = None
370
- if canonical_event == AiHookEventType.FILE_READ and isinstance(tool_input, dict):
371
- file_path = tool_input.get('filePath')
435
+ file_path = read_path if canonical_event == AiHookEventType.FILE_READ else None
372
436
 
373
437
  mcp_server_name = None
374
438
  mcp_tool_name = None
@@ -83,14 +83,6 @@ def scan_command(
83
83
  hidden=True,
84
84
  ),
85
85
  ] = DEFAULT_IDE_NAME,
86
- event: Annotated[
87
- Optional[str],
88
- typer.Option(
89
- '--event',
90
- help='Hook event that triggered the scan, for IDEs whose payloads omit it (e.g. Copilot CLI).',
91
- hidden=True,
92
- ),
93
- ] = None,
94
86
  ) -> None:
95
87
  """Scan content from AI IDE hooks for secrets.
96
88
 
@@ -132,7 +124,7 @@ def scan_command(
132
124
  event_name = unified_payload.event_name
133
125
  logger.debug(
134
126
  'Processing AI guardrails hook',
135
- extra={'event_name': event_name, 'ide': ide_integration.name, 'cli_event_hint': event},
127
+ extra={'event_name': event_name, 'ide': ide_integration.name},
136
128
  )
137
129
 
138
130
  # Resolved before any policy/client work: Copilot hooks have no matchers, so
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cycode
3
- Version: 3.19.1.dev1
3
+ Version: 3.19.2.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
@@ -1,4 +1,4 @@
1
- cycode/__init__.py,sha256=9iMzB3WU7gqdqbYnutD8nIPQgyN6pbFLEna4MQctorQ,396
1
+ cycode/__init__.py,sha256=TENCvPL8ZDRD8qOYdDnksdAW8bSsrOSTCmtnAF7vTuY,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
@@ -11,9 +11,9 @@ cycode/cli/apps/ai_guardrails/hooks_manager.py,sha256=_8EjjpaDoeUrlh6gDXechWyev0
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
13
  cycode/cli/apps/ai_guardrails/ides/base.py,sha256=RZintDpwWgl0l1cBVKccG4ek1jkTMuzRJ-hq91V5ehc,8224
14
- cycode/cli/apps/ai_guardrails/ides/claude_code.py,sha256=ejLGzuVxrnVNOToJl--7HaTFTDI_N3jru8ZC8K0iIo8,15086
14
+ cycode/cli/apps/ai_guardrails/ides/claude_code.py,sha256=eMEL1vjcwGeFZGHqTrnjUBWVn1RHlEyTzrhGPYvuCSw,14977
15
15
  cycode/cli/apps/ai_guardrails/ides/codex.py,sha256=1nI0TPeCgg2zI1PCVmLi0GouD8HGwGoa6RsIshDDa9c,11972
16
- cycode/cli/apps/ai_guardrails/ides/copilot.py,sha256=xjY3jceCwSjFxVygmFwyawXEQT2CVvmiQHYMMA03wXY,19038
16
+ cycode/cli/apps/ai_guardrails/ides/copilot.py,sha256=LcSbZ_5nENYJqxnFJwUlXFQZiHZqxBIjNjin8gIAL_k,22285
17
17
  cycode/cli/apps/ai_guardrails/ides/cursor.py,sha256=-Jr76dD8m2xwvb0i93LtwjaloB_oon0OjBUPkv1K6f0,5633
18
18
  cycode/cli/apps/ai_guardrails/install_command.py,sha256=faNM-5SPpuaKA-9kQ2IdOq9jxJipRfykielgbHk5l-k,4299
19
19
  cycode/cli/apps/ai_guardrails/scan/__init__.py,sha256=qJc82XiQGiAuc1sYY8Ij_A-qXpxgLPuayQq8xWlouMA,48
@@ -21,7 +21,7 @@ cycode/cli/apps/ai_guardrails/scan/consts.py,sha256=drAslw6vW3kxmbCs2qPCUbUPR7PJ
21
21
  cycode/cli/apps/ai_guardrails/scan/handlers.py,sha256=Tu22oCiKAGbNLbypwS7_tUQ5d7O-aYIecKCD7IRgc80,17800
22
22
  cycode/cli/apps/ai_guardrails/scan/payload.py,sha256=pvT3UUqNMvdK3EVzzPjy4JMlOrF-WgxZ3fHN2AtN5eA,1126
23
23
  cycode/cli/apps/ai_guardrails/scan/policy.py,sha256=BZoNNdDQ9tqnfwhB4X1-bDtudaOQc_gXizm3IVwa28o,3351
24
- cycode/cli/apps/ai_guardrails/scan/scan_command.py,sha256=9fzRuItsOoP8ZVzRsCT5z7DVXIRNSeRQEpGmPRlMd5Q,7278
24
+ cycode/cli/apps/ai_guardrails/scan/scan_command.py,sha256=Hs3vkYJztufulFci2N0RNSU6L5UzPMeepts4YgOYggQ,7005
25
25
  cycode/cli/apps/ai_guardrails/scan/types.py,sha256=ybQm242QN0l_4SSNX4xMHXxzqEK-MW-hfIOixI7zvGU,1497
26
26
  cycode/cli/apps/ai_guardrails/scan/utils.py,sha256=QzR_zmivDYwg2-F8g4bFfsycHhNo-pPuJly0P_l0gm8,2879
27
27
  cycode/cli/apps/ai_guardrails/session_start_command.py,sha256=05Li-ON2U1BU_8CR2YYw1y5HVvoWPTf19ne--EgPqmQ,5921
@@ -211,8 +211,8 @@ cycode/cyclient/report_client.py,sha256=Scq30NeJPzgXv0hPLO1U05AdE9i_2iu6cIrSKpEJ
211
211
  cycode/cyclient/scan_client.py,sha256=6TK5FQkfrvV7PHqRnUzEn1PBNd2oPYVamvIixcUfe3c,16755
212
212
  cycode/cyclient/scan_config_base.py,sha256=mXsPZGYCtp85rv5GIige40yQZXuRcEKUW-VQJ0vgFzk,1201
213
213
  cycode/logger.py,sha256=EfZGRK6VC5rE_LAjIcRrHFiQCueylCDXoG6bvGkrIME,2111
214
- cycode-3.19.1.dev1.dist-info/METADATA,sha256=1NLB_A2AupUztGvMt0FV18W9NULYiKwFUFvsm2LLKRg,91052
215
- cycode-3.19.1.dev1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
216
- cycode-3.19.1.dev1.dist-info/entry_points.txt,sha256=iDcVJM8ByLElVgvBgtYxDjw1kT7O8Mo0LcWZIT5L3Ig,45
217
- cycode-3.19.1.dev1.dist-info/licenses/LICENCE,sha256=2Wx4N6mD_4xB7-E3hPkZ3MPhpJy__k_I8MaCSO-PDRo,1068
218
- cycode-3.19.1.dev1.dist-info/RECORD,,
214
+ cycode-3.19.2.dev1.dist-info/METADATA,sha256=BE49FO3m-NeeAeZk1Zd87evnq0xhXBTc_K0mfBtWkA0,91052
215
+ cycode-3.19.2.dev1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
216
+ cycode-3.19.2.dev1.dist-info/entry_points.txt,sha256=iDcVJM8ByLElVgvBgtYxDjw1kT7O8Mo0LcWZIT5L3Ig,45
217
+ cycode-3.19.2.dev1.dist-info/licenses/LICENCE,sha256=2Wx4N6mD_4xB7-E3hPkZ3MPhpJy__k_I8MaCSO-PDRo,1068
218
+ cycode-3.19.2.dev1.dist-info/RECORD,,