From ead7a4fb62816f7d83b7bbdd16838597199bbd59 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 29 Jun 2025 18:44:47 +0100 Subject: [PATCH 01/88] Add server module and update requirements for FastMCP --- codetide/mcp/__init__.py | 0 codetide/mcp/server.py | 8 ++++++++ requirements.txt | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 codetide/mcp/__init__.py create mode 100644 codetide/mcp/server.py diff --git a/codetide/mcp/__init__.py b/codetide/mcp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codetide/mcp/server.py b/codetide/mcp/server.py new file mode 100644 index 0000000..58c303a --- /dev/null +++ b/codetide/mcp/server.py @@ -0,0 +1,8 @@ +from fastmcp import FastMCP + +MCP_USE_INSTRUCTIONS = """""" + +codeTideMCPServer = FastMCP( + name="CodeTide", + # instructions=MCP_USE_INSTRUCTIONS, +) diff --git a/requirements.txt b/requirements.txt index 953e4f9..37197f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ pygit2==1.18.0 pyyaml==6.0.2 tree-sitter==0.24.0 tree-sitter-python==0.23.6 -tree-sitter-typescript==0.23.2 \ No newline at end of file +tree-sitter-typescript==0.23.2 +fastmcp==2.9.2 \ No newline at end of file From e39f18a006412f9d5d667337743f63a0e5065a97 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 29 Jun 2025 18:45:08 +0100 Subject: [PATCH 02/88] Add utility functions for workspace retrieval and safe printing --- codetide/mcp/utils.py | 54 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 codetide/mcp/utils.py diff --git a/codetide/mcp/utils.py b/codetide/mcp/utils.py new file mode 100644 index 0000000..f19d063 --- /dev/null +++ b/codetide/mcp/utils.py @@ -0,0 +1,54 @@ +from ..core.defaults import DEFAULT_SERIALIZATION_PATH +from codetide import CodeTide + +from pathlib import Path +import time +import os + +def getWorkspace()->Path: + """Retrieves and validates the workspace path from CODETIDE_WORKSPACE environment variable.""" + + workspace = os.getenv("CODETIDE_WORKSPACE") + if not workspace: + raise EnvironmentError("codeTideMCPServer requires `CODETIDE_WORKSPACE` env var to be set to your project working directory") + + return Path(workspace) + +def safe_print(string :str): + """Prints string with multiple fallback encodings to handle Unicode errors gracefully.""" + + try: + # First try printing directly + print(string) + except (UnicodeEncodeError, UnicodeError): + try: + # Try with UTF-8 encoding + import sys + if sys.stdout.encoding != 'utf-8': + sys.stdout.reconfigure(encoding='utf-8') # Python 3.7+ + print(string) + except Exception: + # Fallback to ASCII-safe output + print(string.encode('ascii', 'replace').decode('ascii')) + +async def initCodeTide(force_build: bool = False, flush: bool = False)->CodeTide: + """Initializes CodeTide instance either from cache or fresh parse, with serialization options.""" + + workspace = getWorkspace() + storagePath = workspace / DEFAULT_SERIALIZATION_PATH + try: + if force_build: + raise FileNotFoundError() + + tide = CodeTide.deserialize(storagePath) + await tide.check_for_updates(serialize=True, include_cached_ids=True) + if flush: + safe_print(f"[INIT] Initialized from cache: {storagePath}") + + except FileNotFoundError: + st = time.time() + tide = await CodeTide.from_path(rootpath=workspace) + tide.serialize(storagePath, include_cached_ids=True) + if flush: + safe_print(f"[INIT] Fresh parse of {workspace}: {len(tide.codebase.root)} files in {time.time()-st:.2f}s") + return tide \ No newline at end of file From df6571a01749666c3a78d85c100f4bcb093db819 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 29 Jun 2025 19:06:37 +0100 Subject: [PATCH 03/88] Refactor get method in CodeTide to enhance parameter naming and improve docstring clarity --- codetide/__init__.py | 51 +++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/codetide/__init__.py b/codetide/__init__.py index 2685918..f0927d7 100644 --- a/codetide/__init__.py +++ b/codetide/__init__.py @@ -485,36 +485,47 @@ def _precheck_id_is_file(self, unique_ids : List[str])->Dict[Path, str]: if self.rootpath / unique_id in self.files } - def get(self, unique_id :Union[str, List[str]], degree :int=1, slim :bool=False, as_string :bool=True, as_list_str :bool=False)->Union[CodeContextStructure, str, List[str]]: + def get( + self, + code_identifiers: Union[str, List[str]], + context_depth: int = 1, + concise_mode: bool = False, + as_string: bool = True, + as_string_list: bool = False + ) -> Union[CodeContextStructure, str, List[str]]: """ - Retrieve context around code by unique ID(s). + Retrieves code context for given identifiers with flexible return formats. + Returns None if no matching identifiers are found. Args: - unique_id: Single or list of unique IDs for code entities. - degree: Depth of context to fetch. - as_string: Whether to return as a single string. - as_list_str: Whether to return as list of strings. + code_identifiers: One or more code element IDs or file paths to analyze. + Examples: 'package.ClassName', 'dir/module.py:function', ['file.py', 'module.var'] + context_depth: Number of reference levels to include (1=direct references only) + concise_mode: If True, returns minimal docstrings instead of full code (slim=True) + as_string: Return as single formatted string (default) + as_string_list: Return as list of strings (overrides as_string if True) Returns: - Code context in the requested format. + - CodeContextStructure if both format flags are False + - Single concatenated string if as_string=True + - List of context strings if as_string_list=True + - None if no matching identifiers exist """ - if isinstance(unique_id, str): - unique_id = [unique_id] + if isinstance(code_identifiers, str): + code_identifiers = [code_identifiers] - # Log the incoming request logger.info( - f"Getting code context - IDs: {unique_id}, " - f"degree: {degree}, " - f"as_string: {as_string}, " - f"as_list_str: {as_list_str}" + f"Context request - IDs: {code_identifiers}, " + f"Depth: {context_depth}, " + f"Formats: string={as_string}, list={as_string_list}" ) - requestedFiles = self._precheck_id_is_file(unique_id) + requested_files = self._precheck_id_is_file(code_identifiers) return self.codebase.get( - unique_id=unique_id, - degree=degree, - slim=slim, + unique_id=code_identifiers, + degree=context_depth, + slim=concise_mode, as_string=as_string, - as_list_str=as_list_str, - preloaded_files=requestedFiles + as_list_str=as_string_list, + preloaded_files=requested_files ) \ No newline at end of file From 9dec4f028960cbc565efaff1f93b8afe9cb4d18d Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 29 Jun 2025 19:08:35 +0100 Subject: [PATCH 04/88] Initialize __init__.py with server and tools imports, and define __all__ for public API --- codetide/mcp/__init__.py | 7 +++++++ codetide/mcp/tools/__init__.py | 5 +++++ codetide/mcp/tools/get_context.py | 25 +++++++++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 codetide/mcp/tools/__init__.py create mode 100644 codetide/mcp/tools/get_context.py diff --git a/codetide/mcp/__init__.py b/codetide/mcp/__init__.py index e69de29..ff93817 100644 --- a/codetide/mcp/__init__.py +++ b/codetide/mcp/__init__.py @@ -0,0 +1,7 @@ +from .server import codeTideMCPServer +from .tools import getContext + +__all__ = [ + "codeTideMCPServer", + "getContext" +] \ No newline at end of file diff --git a/codetide/mcp/tools/__init__.py b/codetide/mcp/tools/__init__.py new file mode 100644 index 0000000..d928c9b --- /dev/null +++ b/codetide/mcp/tools/__init__.py @@ -0,0 +1,5 @@ +from .get_context import getContext + +__all__ = [ + "getContext" +] \ No newline at end of file diff --git a/codetide/mcp/tools/get_context.py b/codetide/mcp/tools/get_context.py new file mode 100644 index 0000000..30e67c2 --- /dev/null +++ b/codetide/mcp/tools/get_context.py @@ -0,0 +1,25 @@ +from typing import List, Optional, Union +from ..server import codeTideMCPServer +from ..utils import initCodeTide + +@codeTideMCPServer.tool +async def getContext( + code_identifiers: Union[str, List[str]], + context_depth: int = 1) -> Optional[str]: + """ + Retrieves relevant code context for the given code element identifiers. + Returns None if identifiers are invalid. + + Args: + code_identifiers: One or more unique code element IDs or file paths to get context for. + Examples: 'my_module.ClassName', 'path/to/file.py', ['mod.func1', 'mod.func2'] + context_depth: How many levels of related references to include (1=direct references only) + + Returns: + Formatted string containing the requested code context with surrounding relevant code, + or None if identifiers don't exist. + """ + + tide = await initCodeTide() + context = await tide.get(code_identifiers, context_depth, as_string=True) + return context \ No newline at end of file From 464afdfcd9b225a48c2f39bbabaa9e78092c68b4 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 29 Jun 2025 19:25:19 +0100 Subject: [PATCH 05/88] Add getRepoTree function and update public API in __init__.py --- codetide/mcp/__init__.py | 5 ++-- codetide/mcp/tools/__init__.py | 4 ++- codetide/mcp/tools/get_repo_tree.py | 40 +++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 codetide/mcp/tools/get_repo_tree.py diff --git a/codetide/mcp/__init__.py b/codetide/mcp/__init__.py index ff93817..8e0308e 100644 --- a/codetide/mcp/__init__.py +++ b/codetide/mcp/__init__.py @@ -1,7 +1,8 @@ from .server import codeTideMCPServer -from .tools import getContext +from .tools import getContext, getRepoTree __all__ = [ "codeTideMCPServer", - "getContext" + "getContext", + "getRepoTree" ] \ No newline at end of file diff --git a/codetide/mcp/tools/__init__.py b/codetide/mcp/tools/__init__.py index d928c9b..ce5fc41 100644 --- a/codetide/mcp/tools/__init__.py +++ b/codetide/mcp/tools/__init__.py @@ -1,5 +1,7 @@ from .get_context import getContext +from .get_repo_tree import getRepoTree __all__ = [ - "getContext" + "getContext", + "getRepoTree" ] \ No newline at end of file diff --git a/codetide/mcp/tools/get_repo_tree.py b/codetide/mcp/tools/get_repo_tree.py new file mode 100644 index 0000000..e36652a --- /dev/null +++ b/codetide/mcp/tools/get_repo_tree.py @@ -0,0 +1,40 @@ +from ..server import codeTideMCPServer +from ..utils import initCodeTide + +@codeTideMCPServer.tool +async def getRepoTree( + show_contents: bool = False, + show_types: bool = False) -> str: + """ + Generates a visual tree representation of the entire code repository structure. + Useful for understanding the project layout and navigating between files. + + Args: + show_contents: When True, includes classes/functions/variables within files (default: False) + show_types: When True, prefixes items with type codes: + (F=function, V=variable, C=class, A=attribute, M=method) (default: False) + + Returns: + A formatted ASCII tree string showing the repository structure with optional details. + Example output: + ├── src/ + │ ├── utils.py + │ │ └── F helper_function + │ └── main.py + │ └── C MainClass + └── tests/ + └── test_main.py + + Usage Example: + - Basic structure: getRepoTree() + - With contents: getRepoTree(show_contents=True) + - With type markers: getRepoTree(show_types=True) + - Full detail: getRepoTree(show_contents=True, show_types=True) + """ + + tide = await initCodeTide() + result = tide.codebase.get_tree_view( + include_modules=show_contents, + include_types=show_types + ) + return result \ No newline at end of file From b9447c29e57eb10efe026bd24a4f81844188870c Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 29 Jun 2025 19:25:41 +0100 Subject: [PATCH 06/88] Add example script for CodeTide MCP server integration --- examples/codetide_mcp_server.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 examples/codetide_mcp_server.py diff --git a/examples/codetide_mcp_server.py b/examples/codetide_mcp_server.py new file mode 100644 index 0000000..33b5e9a --- /dev/null +++ b/examples/codetide_mcp_server.py @@ -0,0 +1,14 @@ +from codetide.mcp import codeTideMCPServer +from dotenv import load_dotenv + +async def main(): + """ + Make sure to set `CODETIDE_WORKSPACE` env var + """ + tools = await codeTideMCPServer.get_tools() + print(tools) + +if __name__ == "__main__": + import asyncio + load_dotenv() + asyncio.run(main()) \ No newline at end of file From d48a49d055e941ca29e4b0d187617fc6a4107ff2 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 29 Jun 2025 22:49:16 +0100 Subject: [PATCH 07/88] Enhance getContext docstring with detailed argument descriptions and usage examples --- codetide/mcp/tools/get_context.py | 49 ++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/codetide/mcp/tools/get_context.py b/codetide/mcp/tools/get_context.py index 30e67c2..8f32275 100644 --- a/codetide/mcp/tools/get_context.py +++ b/codetide/mcp/tools/get_context.py @@ -11,13 +11,54 @@ async def getContext( Returns None if identifiers are invalid. Args: - code_identifiers: One or more unique code element IDs or file paths to get context for. - Examples: 'my_module.ClassName', 'path/to/file.py', ['mod.func1', 'mod.func2'] - context_depth: How many levels of related references to include (1=direct references only) + code_identifiers: One or more identifiers in dot notation representing: + - Test classes: 'tests.module.test_class.TestClass' + - Test methods: 'tests.module.test_class.TestClass.test_method' + - Core classes: 'package.module.ClassName' + - Class attributes: 'package.module.ClassName.attribute' + - Methods: 'package.module.ClassName.method' + - Setters: 'package.module.ClassName.property@property.setter' + - Services: 'package.services.ServiceName' + - Service methods: 'package.services.ServiceName.execute' + - File paths: 'package/module.py' (forward slashes) + + Common patterns: + - Package.Class: 'package.module.ClassName' + - Package.Class.method: 'package.module.ClassName.method_name' + - Package.Class.attribute: 'package.module.ClassName.attribute_name' + - TestPackage.TestClass.test_method: 'tests.module.TestClass.test_feature' + - ServicePackage.Service.method: 'services.backend.Service.process' + + Examples: + - Single test method: 'tests.parser.test_parser.TestParser.test_file_processing' + - Class with attributes: 'core.models.ImportStatement' + - Multiple elements: [ + 'core.models.FunctionDefinition', + 'tests.parser.test_parser.TestParser.test_imports' + ] + + context_depth: How many reference levels to include: + 0 = Only the requested element(s) (no references) + 1 = Direct references only (default) + 2 = References of references + n = n-level deep reference chain Returns: - Formatted string containing the requested code context with surrounding relevant code, + Formatted string containing: + - The requested code elements + - Their declarations + - Related imports + - Reference implementations + - Syntax-highlighted source code or None if identifiers don't exist. + + Notes: + - For test classes/methods: Use full test path including 'test_' prefix + - For setters: Use @property.setter notation + - Private methods: Include underscore prefix (e.g., '_internal_method') + - File paths must use forward slashes and be relative to repo root + - Case sensitive matching + - When unsure, use getRepoTree() first to discover available identifiers """ tide = await initCodeTide() From 7b450a5b1be7cc5812eb15a982a688734b570362 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 29 Jun 2025 23:21:12 +0100 Subject: [PATCH 08/88] Update getRepoTree function to require show_contents parameter --- codetide/mcp/tools/get_repo_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codetide/mcp/tools/get_repo_tree.py b/codetide/mcp/tools/get_repo_tree.py index e36652a..51e1e61 100644 --- a/codetide/mcp/tools/get_repo_tree.py +++ b/codetide/mcp/tools/get_repo_tree.py @@ -3,7 +3,7 @@ @codeTideMCPServer.tool async def getRepoTree( - show_contents: bool = False, + show_contents: bool, show_types: bool = False) -> str: """ Generates a visual tree representation of the entire code repository structure. From 1a4d334a260d6a397864a732bd1ef54dcb7add31 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 29 Jun 2025 23:28:55 +0100 Subject: [PATCH 09/88] Refactor CodeBase class to inherit from BaseModel instead of RootModel --- codetide/core/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codetide/core/models.py b/codetide/core/models.py index 72033fd..5aa6f2a 100644 --- a/codetide/core/models.py +++ b/codetide/core/models.py @@ -2,7 +2,7 @@ from .defaults import BREAKLINE from .logs import logger -from pydantic import BaseModel, Field, RootModel, computed_field, field_validator +from pydantic import BaseModel, Field, computed_field, field_validator from typing import Any, Dict, List, Optional, Literal, Union from collections import defaultdict import json @@ -536,7 +536,7 @@ def from_list_of_elements(cls, return instance -class CodeBase(RootModel): +class CodeBase(BaseModel): """Root model representing complete codebase with file hierarchy and caching.""" root: List[CodeFileModel] = Field(default_factory=list) _cached_elements :Dict[str, Any] = dict() From 28359c9f6fe7905e7eb7da745a83294eae18c76d Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 29 Jun 2025 23:36:57 +0100 Subject: [PATCH 10/88] Refactor resolve_intra_file_dependencies method to include CodeBase parameter in parser classes --- codetide/__init__.py | 2 +- codetide/parsers/base_parser.py | 2 +- codetide/parsers/generic_parser.py | 2 +- codetide/parsers/python_parser.py | 7 +++++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/codetide/__init__.py b/codetide/__init__.py index f0927d7..cb93aff 100644 --- a/codetide/__init__.py +++ b/codetide/__init__.py @@ -458,7 +458,7 @@ async def check_for_updates(self, if self.rootpath / newFile.file_path in filepaths ] parser.resolve_inter_files_dependencies(self.codebase, filteredNewFiles) - parser.resolve_intra_file_dependencies(filteredNewFiles) + parser.resolve_intra_file_dependencies(self.codebase, filteredNewFiles) for codeFile in filteredNewFiles: i = changedPaths.get(codeFile.file_path) diff --git a/codetide/parsers/base_parser.py b/codetide/parsers/base_parser.py index 729efb0..54ce61a 100644 --- a/codetide/parsers/base_parser.py +++ b/codetide/parsers/base_parser.py @@ -52,7 +52,7 @@ def resolve_inter_files_dependencies(self, codeBase: CodeBase, codeFiles :Option pass @abstractmethod - def resolve_intra_file_dependencies(self, codeFiles: List[CodeFileModel]) -> None: + def resolve_intra_file_dependencies(self, codeBase: CodeBase, codeFiles: List[CodeFileModel], ) -> None: pass # @abstractmethod diff --git a/codetide/parsers/generic_parser.py b/codetide/parsers/generic_parser.py index a5e5b7b..a4faba2 100644 --- a/codetide/parsers/generic_parser.py +++ b/codetide/parsers/generic_parser.py @@ -62,5 +62,5 @@ def parse_code(self, file_path :Path): def resolve_inter_files_dependencies(self, codeBase: CodeBase, codeFiles :Optional[List[CodeFileModel]]=None) -> None: pass - def resolve_intra_file_dependencies(self, codeFiles: List[CodeFileModel]) -> None: + def resolve_intra_file_dependencies(self, codeBase: CodeBase, codeFiles: List[CodeFileModel]) -> None: pass \ No newline at end of file diff --git a/codetide/parsers/python_parser.py b/codetide/parsers/python_parser.py index cb46a0d..f08c212 100644 --- a/codetide/parsers/python_parser.py +++ b/codetide/parsers/python_parser.py @@ -505,9 +505,12 @@ def count_occurences_in_code(code: str, substring: str) -> int: matches = re.findall(pattern, code) return len(matches) - def resolve_intra_file_dependencies(self, codeBase: CodeBase) -> None: + def resolve_intra_file_dependencies(self, codeBase: CodeBase, codeFiles :Optional[List[CodeFileModel]]=None) -> None: codeBase._build_cached_elements() - for codeFile in codeBase.root: + if codeFiles is None: + codeFiles = codeBase.root + + for codeFile in codeFiles: if not codeFile.file_path.endswith(self.extension): continue From 932233acedd902eee9e61e8abfd43842c9ae0766 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 29 Jun 2025 23:37:52 +0100 Subject: [PATCH 11/88] Remove unnecessary await from tide.get call in getContext function --- codetide/mcp/tools/get_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codetide/mcp/tools/get_context.py b/codetide/mcp/tools/get_context.py index 8f32275..fb6b975 100644 --- a/codetide/mcp/tools/get_context.py +++ b/codetide/mcp/tools/get_context.py @@ -62,5 +62,5 @@ async def getContext( """ tide = await initCodeTide() - context = await tide.get(code_identifiers, context_depth, as_string=True) + context = tide.get(code_identifiers, context_depth, as_string=True) return context \ No newline at end of file From 044cd2e969ddd74402837e1ff8931ad66a0adfb5 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 29 Jun 2025 23:45:43 +0100 Subject: [PATCH 12/88] Add initial implementation of agent_tide example with LLM integration --- .gitignore | 1 + examples/agent_tide.py | 35 +++++++++++++++++++++++++++++++++++ examples/aicore_dashboard.py | 5 +++++ 3 files changed, 41 insertions(+) create mode 100644 examples/agent_tide.py create mode 100644 examples/aicore_dashboard.py diff --git a/.gitignore b/.gitignore index c4691c8..d76479b 100644 --- a/.gitignore +++ b/.gitignore @@ -175,4 +175,5 @@ cython_debug/ storage/ logs/ +observability_data/ diff --git a/examples/agent_tide.py b/examples/agent_tide.py new file mode 100644 index 0000000..fe20615 --- /dev/null +++ b/examples/agent_tide.py @@ -0,0 +1,35 @@ +""" +install aicore - pip install core-for-ai +Make sure to set `CODETIDE_WORKSPACE` env var which will be requried for the MCP server to work +""" + +from aicore.llm import Llm, LlmConfig +from codetide.mcp import codeTideMCPServer +import os + +async def main(): + llm = Llm.from_config( + LlmConfig( + model="deepseek-chat", + provider="deepseek", + temperature=0, + api_key=os.getenv("DEEPSEEK-API-KEY") + ) + ) + + llm.provider.mcp.add_server(name=codeTideMCPServer.name, parameters=codeTideMCPServer) + await llm.acomplete( + prompt="How many parsers are currently supported?", + system_prompt=["You are a software engineer with tool calling capabilities who will help the user navigate his codebase"], + agent_id="agent-tide", + action_id="warm-up" + ) + +if __name__ == "__main__": + from dotenv import load_dotenv + import asyncio + + load_dotenv() + asyncio.run(main()) + + diff --git a/examples/aicore_dashboard.py b/examples/aicore_dashboard.py new file mode 100644 index 0000000..c91bdcf --- /dev/null +++ b/examples/aicore_dashboard.py @@ -0,0 +1,5 @@ +from aicore.observability import ObservabilityDashboard + +if __name__ == "__main__": + od = ObservabilityDashboard(from_local_records_only=True) + od.run_server() \ No newline at end of file From 84000c30b589424eae06c4d5ef4d9454cb0a6d9f Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 29 Jun 2025 23:49:06 +0100 Subject: [PATCH 13/88] Update return type of getContext function to str and enhance error message; refine docstring for getRepoTree function --- codetide/mcp/tools/get_context.py | 6 +++--- codetide/mcp/tools/get_repo_tree.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/codetide/mcp/tools/get_context.py b/codetide/mcp/tools/get_context.py index fb6b975..2f1effa 100644 --- a/codetide/mcp/tools/get_context.py +++ b/codetide/mcp/tools/get_context.py @@ -1,11 +1,11 @@ -from typing import List, Optional, Union +from typing import List, Union from ..server import codeTideMCPServer from ..utils import initCodeTide @codeTideMCPServer.tool async def getContext( code_identifiers: Union[str, List[str]], - context_depth: int = 1) -> Optional[str]: + context_depth: int = 1) -> str: """ Retrieves relevant code context for the given code element identifiers. Returns None if identifiers are invalid. @@ -63,4 +63,4 @@ async def getContext( tide = await initCodeTide() context = tide.get(code_identifiers, context_depth, as_string=True) - return context \ No newline at end of file + return context if context else f"{code_identifiers} are not valid code_identifiers, refer to the getRepoTree tool to get a sense of the correct identifiers" \ No newline at end of file diff --git a/codetide/mcp/tools/get_repo_tree.py b/codetide/mcp/tools/get_repo_tree.py index 51e1e61..ff4096a 100644 --- a/codetide/mcp/tools/get_repo_tree.py +++ b/codetide/mcp/tools/get_repo_tree.py @@ -10,7 +10,7 @@ async def getRepoTree( Useful for understanding the project layout and navigating between files. Args: - show_contents: When True, includes classes/functions/variables within files (default: False) + show_contents: When True, includes classes/functions/variables within files show_types: When True, prefixes items with type codes: (F=function, V=variable, C=class, A=attribute, M=method) (default: False) From 1d97a3eeacb7d323aaf2d8e0608a02348bf44bef Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Mon, 30 Jun 2025 20:53:58 +0100 Subject: [PATCH 14/88] Improve logging in CodeBase class by conditionally logging retrieved elements count --- codetide/core/models.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/codetide/core/models.py b/codetide/core/models.py index 5aa6f2a..98c6360 100644 --- a/codetide/core/models.py +++ b/codetide/core/models.py @@ -842,12 +842,9 @@ def get(self, references_types = new_references_types.copy() first_swipe = False degree -= 1 - - logger.info(f"Retrieved {len(retrieved_elements)} total elements") - - # for _id, _type in zip(retrieved_ids, retrieved_elements_reference_type): - # if _type: - # print(f"{_id} -> {_type}") + + if retrieved_elements: + logger.info(f"Retrieved {len(retrieved_elements)} total elements") assert len(retrieved_elements_reference_type) == len(retrieved_elements), "Mismatch between retrieved elements and retrieved reference types" codeContext = CodeContextStructure.from_list_of_elements( From 7fa0b154d5eafad21a07da6a3a942c7a14bd0b8c Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Mon, 30 Jun 2025 21:02:31 +0100 Subject: [PATCH 15/88] Replace json with orjson for improved performance in serialization and deserialization; update requirements.txt to include orjson --- codetide/__init__.py | 6 +++--- codetide/core/models.py | 8 ++++---- requirements.txt | 1 + 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/codetide/__init__.py b/codetide/__init__.py index cb93aff..87d1d6c 100644 --- a/codetide/__init__.py +++ b/codetide/__init__.py @@ -17,7 +17,7 @@ import asyncio import pygit2 import time -import json +import orjson import os class CodeTide(BaseModel): @@ -138,7 +138,7 @@ def serialize(self, if include_cached_ids: cached_ids_path = dir_path / DEFAULT_CACHED_IDS_FILE - writeFile(json.dumps(self.codebase.unique_ids+self.relative_filepaths, indent=4), cached_ids_path) + writeFile(orjson.dumps(self.codebase.unique_ids+self.relative_filepaths, option=orjson.OPT_INDENT_2), cached_ids_path) @classmethod def deserialize(cls, filepath :Optional[Union[str, Path]]=DEFAULT_SERIALIZATION_PATH, rootpath :Optional[Union[str, Path]] = None)->"CodeTide": @@ -158,7 +158,7 @@ def deserialize(cls, filepath :Optional[Union[str, Path]]=DEFAULT_SERIALIZATION_ if not os.path.exists(filepath): raise FileNotFoundError(f"{filepath} is not a valid path") - kwargs = json.loads(readFile(filepath)) + kwargs = orjson.loads(readFile(filepath)) tideInstance = cls(**kwargs) # dir_path = Path(os.path.split(filepath))[0] diff --git a/codetide/core/models.py b/codetide/core/models.py index 98c6360..fdf4e32 100644 --- a/codetide/core/models.py +++ b/codetide/core/models.py @@ -5,7 +5,7 @@ from pydantic import BaseModel, Field, computed_field, field_validator from typing import Any, Dict, List, Optional, Literal, Union from collections import defaultdict -import json +import orjson class BaseCodeElement(BaseModel): """Base class representing any code element with file path and raw content handling.""" @@ -875,15 +875,15 @@ def get(self, def serialize_cache_elements(self, indent :int=4)->str: """Serializes cached elements to JSON for storage.""" - return json.dumps( + return orjson.dumps( { key: value.model_dump() for key, value in self._cached_elements - } + }, option=orjson.OPT_INDENT_2 ) def deserialize_cache_elements(self, contents :str): - self._cached_elements = json.loads(contents) + self._cached_elements = orjson.loads(contents) ### TODO need to handle model validates and so on # return json.dumps( # { diff --git a/requirements.txt b/requirements.txt index 37197f1..fa12af6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ loguru==0.7.3 +orjson==3.10.13 pathspec==0.12.1 pydantic==2.10.3 pygit2==1.18.0 From dc244ea08d46d5ba825928cbab8972008ffe6b02 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Mon, 30 Jun 2025 22:37:03 +0100 Subject: [PATCH 16/88] Enhance AutoComplete class with type hints and add validate_code_identifier method for improved code validation and suggestion matching --- codetide/autocomplete.py | 77 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/codetide/autocomplete.py b/codetide/autocomplete.py index fb28ca5..3a8c74a 100644 --- a/codetide/autocomplete.py +++ b/codetide/autocomplete.py @@ -1,11 +1,13 @@ +from typing import List +import difflib class AutoComplete: - def __init__(self, word_list): + def __init__(self, word_list: List[str]) -> None: """Initialize with a list of strings to search from""" self.words = word_list # Sort words for better organization (optional) self.words.sort() - def get_suggestions(self, prefix, max_suggestions=10, case_sensitive=False): + def get_suggestions(self, prefix: str, max_suggestions: int = 10, case_sensitive: bool = False) -> List[str]: """ Get autocomplete suggestions based on prefix @@ -35,7 +37,7 @@ def get_suggestions(self, prefix, max_suggestions=10, case_sensitive=False): return suggestions - def get_fuzzy_suggestions(self, prefix, max_suggestions=10, case_sensitive=False): + def get_fuzzy_suggestions(self, prefix: str, max_suggestions: int = 10, case_sensitive: bool = False) -> List[str]: """ Get suggestions that contain the prefix anywhere in the string @@ -63,3 +65,72 @@ def get_fuzzy_suggestions(self, prefix, max_suggestions=10, case_sensitive=False break return suggestions + + def validate_code_identifier(self, code_identifier, max_suggestions=5, case_sensitive=False): + """ + Validate a code identifier and return similar matches if not found + + Args: + code_identifier (str): The code identifier to validate + max_suggestions (int): Maximum number of similar suggestions to return + case_sensitive (bool): Whether matching should be case sensitive + + Returns: + dict: Dictionary with validation result + """ + if not code_identifier: + return { + "code_identifier": code_identifier, + "is_valid": False, + "matching_identifiers": [] + } + + # Check for perfect match + search_word = code_identifier if case_sensitive else code_identifier.lower() + words_to_check = self.words if case_sensitive else [word.lower() for word in self.words] + + if search_word in words_to_check: + return { + "code_identifier": code_identifier, + "is_valid": True, + "matching_identifiers": [] + } + + # If not perfect match, find most similar ones using difflib + # Get close matches using sequence matching + close_matches = difflib.get_close_matches( + code_identifier, + self.words, + n=max_suggestions, + cutoff=0.3 # Minimum similarity threshold + ) + + # If we don't have enough close matches, supplement with fuzzy matches + if len(close_matches) < max_suggestions: + fuzzy_matches = self.get_fuzzy_suggestions( + code_identifier, + max_suggestions * 2, # Get more to filter + case_sensitive + ) + + # Add fuzzy matches that aren't already in close_matches + for match in fuzzy_matches: + if match not in close_matches and len(close_matches) < max_suggestions: + close_matches.append(match) + + # Sort by similarity score (difflib ratio) + if close_matches: + similarity_scores = [] + for match in close_matches: + score = difflib.SequenceMatcher(None, code_identifier, match).ratio() + similarity_scores.append((match, score)) + + # Sort by score descending (most similar first) + similarity_scores.sort(key=lambda x: x[1], reverse=True) + close_matches = [match for match, score in similarity_scores[:max_suggestions]] + + return { + "code_identifier": code_identifier, + "is_valid": False, + "matching_identifiers": close_matches + } \ No newline at end of file From 926b058c253d3c69faffc90842b38c98c05ba677 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Mon, 30 Jun 2025 22:37:32 +0100 Subject: [PATCH 17/88] Add cached_ids property to CodeTide and update serialization methods for improved data handling --- codetide/__init__.py | 6 +++++- codetide/core/models.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/codetide/__init__.py b/codetide/__init__.py index 87d1d6c..4782045 100644 --- a/codetide/__init__.py +++ b/codetide/__init__.py @@ -90,6 +90,10 @@ def relative_filepaths(self)->List[str]: str(filepath.relative_to(self.rootpath)).replace("\\", "/") for filepath in self.files ] + @property + def cached_ids(self)->List[str]: + return self.codebase.unique_ids+self.relative_filepaths + async def _reset(self): self = await self.from_path(self.rootpath) @@ -138,7 +142,7 @@ def serialize(self, if include_cached_ids: cached_ids_path = dir_path / DEFAULT_CACHED_IDS_FILE - writeFile(orjson.dumps(self.codebase.unique_ids+self.relative_filepaths, option=orjson.OPT_INDENT_2), cached_ids_path) + writeFile(str(orjson.dumps(self.cached_ids, option=orjson.OPT_INDENT_2)), cached_ids_path) @classmethod def deserialize(cls, filepath :Optional[Union[str, Path]]=DEFAULT_SERIALIZATION_PATH, rootpath :Optional[Union[str, Path]] = None)->"CodeTide": diff --git a/codetide/core/models.py b/codetide/core/models.py index fdf4e32..e98dde0 100644 --- a/codetide/core/models.py +++ b/codetide/core/models.py @@ -875,12 +875,12 @@ def get(self, def serialize_cache_elements(self, indent :int=4)->str: """Serializes cached elements to JSON for storage.""" - return orjson.dumps( + return str(orjson.dumps( { key: value.model_dump() for key, value in self._cached_elements }, option=orjson.OPT_INDENT_2 - ) + )) def deserialize_cache_elements(self, contents :str): self._cached_elements = orjson.loads(contents) From 16aab9ae5c383aa4a5fdad293cc1dacb3dc2e57f Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Mon, 30 Jun 2025 22:47:31 +0100 Subject: [PATCH 18/88] Add checkCodeIdentifiers function for validating code identifiers and update exports --- codetide/mcp/__init__.py | 5 +- codetide/mcp/tools/__init__.py | 4 +- codetide/mcp/tools/check_code_identifiers.py | 63 ++++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 codetide/mcp/tools/check_code_identifiers.py diff --git a/codetide/mcp/__init__.py b/codetide/mcp/__init__.py index 8e0308e..7407264 100644 --- a/codetide/mcp/__init__.py +++ b/codetide/mcp/__init__.py @@ -1,8 +1,9 @@ from .server import codeTideMCPServer -from .tools import getContext, getRepoTree +from .tools import getContext, getRepoTree, checkCodeIdentifiers __all__ = [ "codeTideMCPServer", "getContext", - "getRepoTree" + "getRepoTree", + "checkCodeIdentifiers" ] \ No newline at end of file diff --git a/codetide/mcp/tools/__init__.py b/codetide/mcp/tools/__init__.py index ce5fc41..36a4e3a 100644 --- a/codetide/mcp/tools/__init__.py +++ b/codetide/mcp/tools/__init__.py @@ -1,7 +1,9 @@ from .get_context import getContext from .get_repo_tree import getRepoTree +from .check_code_identifiers import checkCodeIdentifiers __all__ = [ "getContext", - "getRepoTree" + "getRepoTree", + "checkCodeIdentifiers" ] \ No newline at end of file diff --git a/codetide/mcp/tools/check_code_identifiers.py b/codetide/mcp/tools/check_code_identifiers.py new file mode 100644 index 0000000..43f0b26 --- /dev/null +++ b/codetide/mcp/tools/check_code_identifiers.py @@ -0,0 +1,63 @@ +from ...autocomplete import AutoComplete +from ..utils import initCodeTide +from ..server import codeTideMCPServer + +from typing import List +import orjson + +@codeTideMCPServer.tool +async def checkCodeIdentifiers(code_identifiers: List[str]) -> str: + """ + Validates code identifiers against cached repository entries and suggests corrections. + + Args: + code_identifiers: List of identifiers in dot or slash notation, such as: + - 'tests.module.TestClass.test_method' + - 'package.module.Class.method_or_attr' + - 'services.module.Service.execute' + - 'package/module.py' + + Returns: + List of dictionaries: + - code_identifier (str): The identifier checked + - is_valid (bool): True if valid + - matching_identifiers (List[str]): Up to 5 similar entries if invalid + + Example: + [ + { + "code_identifier": "core.models.ImportStatement", + "is_valid": True, + "matching_identifiers": [] + }, + { + "code_identifier": "core.models.ImportStatment", + "is_valid": False, + "matching_identifiers": [ + "core.models.ImportStatement", + "core.models.FunctionStatement", + "core.models.ClassStatement" + ] + } + ] + + Notes: + - Identifiers are case-sensitive + - Similarity ranked by relevance (fuzzy + sequence match) + - Setters use @property.setter syntax + - File paths must be forward slashed and repo-relative + - Use getRepoTree() to explore valid identifiers + """ + + tide = await initCodeTide() + + # Initialize AutoComplete + autocomplete = AutoComplete(tide.cached_ids) + + # Validate each code identifier + results = [] + for code_id in code_identifiers: + result = autocomplete.validate_code_identifier(code_id) + results.append(result) + + return str(orjson.dumps(results, option=orjson.OPT_INDENT_2)) \ No newline at end of file From c21d0384f8ebab3cb57059f04a3a471f6165c322 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Mon, 30 Jun 2025 22:49:12 +0100 Subject: [PATCH 19/88] Refactor getContext docstring for clarity and conciseness in parameter descriptions --- codetide/mcp/tools/get_context.py | 68 +++++++++++-------------------- 1 file changed, 23 insertions(+), 45 deletions(-) diff --git a/codetide/mcp/tools/get_context.py b/codetide/mcp/tools/get_context.py index 2f1effa..e3489f6 100644 --- a/codetide/mcp/tools/get_context.py +++ b/codetide/mcp/tools/get_context.py @@ -7,58 +7,36 @@ async def getContext( code_identifiers: Union[str, List[str]], context_depth: int = 1) -> str: """ - Retrieves relevant code context for the given code element identifiers. - Returns None if identifiers are invalid. + Retrieves code context for one or more identifiers. Returns None if invalid. Args: - code_identifiers: One or more identifiers in dot notation representing: - - Test classes: 'tests.module.test_class.TestClass' - - Test methods: 'tests.module.test_class.TestClass.test_method' - - Core classes: 'package.module.ClassName' - - Class attributes: 'package.module.ClassName.attribute' - - Methods: 'package.module.ClassName.method' - - Setters: 'package.module.ClassName.property@property.setter' - - Services: 'package.services.ServiceName' - - Service methods: 'package.services.ServiceName.execute' - - File paths: 'package/module.py' (forward slashes) - - Common patterns: - - Package.Class: 'package.module.ClassName' - - Package.Class.method: 'package.module.ClassName.method_name' - - Package.Class.attribute: 'package.module.ClassName.attribute_name' - - TestPackage.TestClass.test_method: 'tests.module.TestClass.test_feature' - - ServicePackage.Service.method: 'services.backend.Service.process' + code_identifiers: Dot- or slash-notated identifiers, such as: + - 'tests.module.TestClass.test_method' + - 'package.module.Class.method_or_attr' + - 'services.backend.Service.execute' + - 'package/module.py' + - Or a list of such identifiers - Examples: - - Single test method: 'tests.parser.test_parser.TestParser.test_file_processing' - - Class with attributes: 'core.models.ImportStatement' - - Multiple elements: [ - 'core.models.FunctionDefinition', - 'tests.parser.test_parser.TestParser.test_imports' - ] - - context_depth: How many reference levels to include: - 0 = Only the requested element(s) (no references) - 1 = Direct references only (default) - 2 = References of references - n = n-level deep reference chain + context_depth: Reference depth to include: + 0 – Only the requested element(s) + 1 – Direct references (default) + 2+ – Recursive reference levels Returns: - Formatted string containing: - - The requested code elements - - Their declarations - - Related imports - - Reference implementations - - Syntax-highlighted source code - or None if identifiers don't exist. + A formatted string with: + - Declarations + - Imports + - Related references + - Syntax-highlighted code + Returns None if identifiers are not found. Notes: - - For test classes/methods: Use full test path including 'test_' prefix - - For setters: Use @property.setter notation - - Private methods: Include underscore prefix (e.g., '_internal_method') - - File paths must use forward slashes and be relative to repo root - - Case sensitive matching - - When unsure, use getRepoTree() first to discover available identifiers + - Identifiers are case-sensitive + - Use full paths for test elements and include 'test_' prefix + - Setters: Use @property.setter format + - Private methods: Include leading underscore + - File paths must be forward slashes, repo-relative + - Use getRepoTree() to explore available identifiers """ tide = await initCodeTide() From 401a218cf0756350aa91306cc149151e1d4705b3 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Mon, 30 Jun 2025 23:18:18 +0100 Subject: [PATCH 20/88] Refactor agent_tide.py to improve structure and readability; separate LLM initialization and message trimming into distinct functions --- examples/agent_tide.py | 48 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/examples/agent_tide.py b/examples/agent_tide.py index fe20615..e172a73 100644 --- a/examples/agent_tide.py +++ b/examples/agent_tide.py @@ -4,10 +4,12 @@ """ from aicore.llm import Llm, LlmConfig +from aicore.logger import _logger from codetide.mcp import codeTideMCPServer +from typing import Optional import os -async def main(): +def init_llm()->Llm: llm = Llm.from_config( LlmConfig( model="deepseek-chat", @@ -18,12 +20,42 @@ async def main(): ) llm.provider.mcp.add_server(name=codeTideMCPServer.name, parameters=codeTideMCPServer) - await llm.acomplete( - prompt="How many parsers are currently supported?", - system_prompt=["You are a software engineer with tool calling capabilities who will help the user navigate his codebase"], - agent_id="agent-tide", - action_id="warm-up" - ) + return llm + +def trim_messages(messages, tokenizer_fn, max_tokens :Optional[int]=None): + max_tokens = max_tokens or int(os.environ.get("MAX_HISTORY_TOKENS", 1028)) + while messages and sum(len(tokenizer_fn(msg)) for msg in messages) > max_tokens: + messages.pop(0) # Remove from the beginning + +AGENT_TIDE_SYSTEM_PROMPT = """ +You are a software engineer with tool calling capabilities who will help the user navigate his codebase +""" + +async def main(max_tokens :int=48000): + llm = init_llm() + history = [] + + _logger.logger.info("Agent is ready. Press ESC to exit.\n") + try: + while True: + try: + message = input("You: ").strip() + if not message: + continue + except EOFError: + break # Ctrl+D on Unix, Ctrl+Z on Windows + except KeyboardInterrupt: + _logger.logger.warning("\nExiting...") + break + + history.append(message) + trim_messages(history, llm.tokenizer, max_tokens) + + response = await llm.acomplete(history, system_prompt=[AGENT_TIDE_SYSTEM_PROMPT]) + history.append(response) + + except KeyboardInterrupt: + _logger.logger.warning("\nExited by user.") if __name__ == "__main__": from dotenv import load_dotenv @@ -31,5 +63,3 @@ async def main(): load_dotenv() asyncio.run(main()) - - From 44ae5668ae6bbf75fa8cbe0b953a4880f6b548a6 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Mon, 30 Jun 2025 23:56:28 +0100 Subject: [PATCH 21/88] Add ASCII art headers to CodeTide and AgentTide for enhanced visual appeal --- codetide/__init__.py | 4 ++-- codetide/core/defaults.py | 13 ++++++++++++- examples/agent_tide.py | 13 ++++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/codetide/__init__.py b/codetide/__init__.py index 4782045..d3ad751 100644 --- a/codetide/__init__.py +++ b/codetide/__init__.py @@ -1,5 +1,5 @@ from codetide.core.defaults import ( - DEFAULT_SERIALIZATION_PATH, DEFAULT_MAX_CONCURRENT_TASKS, + CODETIDE_ASCII_ART, DEFAULT_SERIALIZATION_PATH, DEFAULT_MAX_CONCURRENT_TASKS, DEFAULT_BATCH_SIZE, DEFAULT_CACHED_ELEMENTS_FILE, DEFAULT_CACHED_IDS_FILE, LANGUAGE_EXTENSIONS ) @@ -80,7 +80,7 @@ async def from_path( codeTide._add_results_to_codebase(results) codeTide._resolve_files_dependencies() - logger.info(f"CodeTide initialized with {len(results)} files processed in {time.time() - st:.2f}s") + logger.info(f"\n{CODETIDE_ASCII_ART}\nInitialized with {len(results)} files processed in {time.time() - st:.2f}s") return codeTide diff --git a/codetide/core/defaults.py b/codetide/core/defaults.py index 0f57ab9..f1f4e60 100644 --- a/codetide/core/defaults.py +++ b/codetide/core/defaults.py @@ -54,4 +54,15 @@ DEFAULT_CACHED_ELEMENTS_FILE = "cached_elements.json" DEFAULT_CACHED_IDS_FILE = "cached_ids.json" -BREAKLINE = "\n" \ No newline at end of file +BREAKLINE = "\n" + +CODETIDE_ASCII_ART = """ + +███████╗ ██████╗ ██████╗ ███████╗████████╗██╗██████╗ ███████╗ +██╔════╝██╔═══██╗██╔══██╗██╔════╝╚══██╔══╝██║██╔══██╗██╔════╝ +██║ ██║ ██║██║ ██║█████╗ ██║ ██║██║ ██║█████╗ +██║ ██║ ██║██║ ██║██╔══╝ ██║ ██║██║ ██║██╔══╝ +╚██████╗╚██████╔╝██████╔╝███████╗ ██║ ██║██████╔╝███████╗ + ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝ + +""" \ No newline at end of file diff --git a/examples/agent_tide.py b/examples/agent_tide.py index e172a73..a74fd47 100644 --- a/examples/agent_tide.py +++ b/examples/agent_tide.py @@ -9,6 +9,17 @@ from typing import Optional import os +AGENT_TIDE_ASCII_ART = """ + +█████╗ ██████╗ ███████╗███╗ ██╗████████╗ ████████╗██╗██████╗ ███████╗ +██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝ ╚══██╔══╝██║██╔══██╗██╔════╝ +███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ██║ ██║██║ ██║█████╗ +██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ██║ ██║██║ ██║██╔══╝ +██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ██║ ██║██████╔╝███████╗ +╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝ + +""" + def init_llm()->Llm: llm = Llm.from_config( LlmConfig( @@ -35,7 +46,7 @@ async def main(max_tokens :int=48000): llm = init_llm() history = [] - _logger.logger.info("Agent is ready. Press ESC to exit.\n") + _logger.logger.info(F"\n{AGENT_TIDE_ASCII_ART}\nReady to surf. Press ESC to exit.\n") try: while True: try: From 205cf8a7c5d144a30584629194e2bf4e889e53fe Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Tue, 1 Jul 2025 23:41:24 +0100 Subject: [PATCH 22/88] Implement patch application utility and related parsing functionality --- codetide/mcp/tools/apply_patch.py | 67 +++++++ .../mcp/tools/openai_appy_patch/__init__.py | 106 +++++++++++ .../mcp/tools/openai_appy_patch/models.py | 60 +++++++ .../mcp/tools/openai_appy_patch/parser.py | 170 ++++++++++++++++++ codetide/mcp/tools/openai_appy_patch/utils.py | 167 +++++++++++++++++ 5 files changed, 570 insertions(+) create mode 100644 codetide/mcp/tools/apply_patch.py create mode 100644 codetide/mcp/tools/openai_appy_patch/__init__.py create mode 100644 codetide/mcp/tools/openai_appy_patch/models.py create mode 100644 codetide/mcp/tools/openai_appy_patch/parser.py create mode 100644 codetide/mcp/tools/openai_appy_patch/utils.py diff --git a/codetide/mcp/tools/apply_patch.py b/codetide/mcp/tools/apply_patch.py new file mode 100644 index 0000000..f26eae3 --- /dev/null +++ b/codetide/mcp/tools/apply_patch.py @@ -0,0 +1,67 @@ +from .openai_appy_patch import process_patch, open_file, write_file, remove_file +from ..server import codeTideMCPServer +from ..utils import initCodeTide + +@codeTideMCPServer.tool +async def applyPatch(input :str)->str: + """ + This is a custom utility that makes it more convenient to add, remove, move, or edit code files. + `applyPatch` effectively allows you to execute a diff/patch against a file, but the format of the diff specification is unique to this task, so pay careful attention to these instructions. + To use the `applyPatch` command, you should pass a message of the following structure as "input": + + %%bash + applyPatch <<"EOF" + *** Begin Patch + [YOUR_PATCH] + *** End Patch + EOF + + Where [YOUR_PATCH] is the actual content of your patch, specified in the following V4A diff format. + + *** [ACTION] File: [path/to/file] -> ACTION can be one of Add, Update, or Delete. + For each snippet of code that needs to be changed, repeat the following: + [context_before] -> See below for further instructions on context. + - [old_code] -> Precede the old code with a minus sign. + + [new_code] -> Precede the new, replacement code with a plus sign. + [context_after] -> See below for further instructions on context. + + For instructions on [context_before] and [context_after]: + - By default, show 3 lines of code immediately above and 3 lines immediately below each change. If a change is within 3 lines of a previous change, do NOT duplicate the first change’s [context_after] lines in the second change’s [context_before] lines. + - If 3 lines of context is insufficient to uniquely identify the snippet of code within the file, use the @@ operator to indicate the class or function to which the snippet belongs. For instance, we might have: + @@ class BaseClass + [3 lines of pre-context] + - [old_code] + + [new_code] + [3 lines of post-context] + + - If a code block is repeated so many times in a class or function such that even a single @@ statement and 3 lines of context cannot uniquely identify the snippet of code, you can use multiple `@@` statements to jump to the right context. For instance: + + @@ class BaseClass + @@ def method(): + [3 lines of pre-context] + - [old_code] + + [new_code] + [3 lines of post-context] + + Note, then, that we do not use line numbers in this diff format, as the context is enough to uniquely identify code. An example of a message that you might pass as "input" to this function, in order to apply a patch, is shown below. + + %%bash + applyPatch <<"EOF" + *** Begin Patch + *** Update File: pygorithm/searching/binary_search.py + @@ class BaseClass + @@ def search(): + - pass + + raise NotImplementedError() + + @@ class Subclass + @@ def search(): + - pass + + raise NotImplementedError() + + *** End Patch + EOF + """ + result = process_patch(input, open_file, write_file, remove_file) + _ = await initCodeTide() + return result diff --git a/codetide/mcp/tools/openai_appy_patch/__init__.py b/codetide/mcp/tools/openai_appy_patch/__init__.py new file mode 100644 index 0000000..be545b5 --- /dev/null +++ b/codetide/mcp/tools/openai_appy_patch/__init__.py @@ -0,0 +1,106 @@ +from .models import DiffError, Patch, Commit, ActionType +from .utils import patch_to_commit +from .parser import Parser + +from typing import Dict, Tuple, List, Callable +import pathlib + +# --------------------------------------------------------------------------- # +# User-facing helpers +# --------------------------------------------------------------------------- # +def text_to_patch(text: str, orig: Dict[str, str]) -> Tuple[Patch, int]: + lines = text.splitlines() # preserves blank lines, no strip() + if ( + len(lines) < 2 + or not Parser._norm(lines[0]).startswith("*** Begin Patch") + or Parser._norm(lines[-1]) != "*** End Patch" + ): + raise DiffError("Invalid patch text - missing sentinels") + + parser = Parser(current_files=orig, lines=lines, index=1) + parser.parse() + return parser.patch, parser.fuzz + + +def identify_files_needed(text: str) -> List[str]: + lines = text.splitlines() + return [ + line[len("*** Update File: ") :] + for line in lines + if line.startswith("*** Update File: ") + ] + [ + line[len("*** Delete File: ") :] + for line in lines + if line.startswith("*** Delete File: ") + ] + + +def identify_files_added(text: str) -> List[str]: + lines = text.splitlines() + return [ + line[len("*** Add File: ") :] + for line in lines + if line.startswith("*** Add File: ") + ] + +# --------------------------------------------------------------------------- # +# File-system helpers +# --------------------------------------------------------------------------- # +def load_files(paths: List[str], open_fn: Callable[[str], str]) -> Dict[str, str]: + return {path: open_fn(path) for path in paths} + + +def apply_commit( + commit: Commit, + write_fn: Callable[[str, str], None], + remove_fn: Callable[[str], None], +) -> None: + for path, change in commit.changes.items(): + if change.type is ActionType.DELETE: + remove_fn(path) + elif change.type is ActionType.ADD: + if change.new_content is None: + raise DiffError(f"ADD change for {path} has no content") + write_fn(path, change.new_content) + elif change.type is ActionType.UPDATE: + if change.new_content is None: + raise DiffError(f"UPDATE change for {path} has no new content") + target = change.move_path or path + write_fn(target, change.new_content) + if change.move_path: + remove_fn(path) + + +def process_patch( + text: str, + open_fn: Callable[[str], str], + write_fn: Callable[[str, str], None], + remove_fn: Callable[[str], None], +) -> str: + if not text.startswith("*** Begin Patch"): + raise DiffError("Patch text must start with *** Begin Patch") + paths = identify_files_needed(text) + orig = load_files(paths, open_fn) + patch, _fuzz = text_to_patch(text, orig) + commit = patch_to_commit(patch, orig) + apply_commit(commit, write_fn, remove_fn) + return "Done!" + + +# --------------------------------------------------------------------------- # +# Default FS helpers +# --------------------------------------------------------------------------- # +def open_file(path: str) -> str: + with open(path, "rt", encoding="utf-8") as fh: + return fh.read() + + +def write_file(path: str, content: str) -> None: + target = pathlib.Path(path) + target.parent.mkdir(parents=True, exist_ok=True) + with target.open("wt", encoding="utf-8") as fh: + fh.write(content) + + +def remove_file(path: str) -> None: + pathlib.Path(path).unlink(missing_ok=True) \ No newline at end of file diff --git a/codetide/mcp/tools/openai_appy_patch/models.py b/codetide/mcp/tools/openai_appy_patch/models.py new file mode 100644 index 0000000..ce375fb --- /dev/null +++ b/codetide/mcp/tools/openai_appy_patch/models.py @@ -0,0 +1,60 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import ( + Dict, + List, + Optional, +) + +# --------------------------------------------------------------------------- # +# Domain objects +# --------------------------------------------------------------------------- # +class ActionType(str, Enum): + ADD = "add" + DELETE = "delete" + UPDATE = "update" + + +@dataclass +class FileChange: + type: ActionType + old_content: Optional[str] = None + new_content: Optional[str] = None + move_path: Optional[str] = None + + +@dataclass +class Commit: + changes: Dict[str, FileChange] = field(default_factory=dict) + + +# --------------------------------------------------------------------------- # +# Exceptions +# --------------------------------------------------------------------------- # +class DiffError(ValueError): + """Any problem detected while parsing or applying a patch.""" + + +# --------------------------------------------------------------------------- # +# Helper dataclasses used while parsing patches +# --------------------------------------------------------------------------- # +@dataclass +class Chunk: + orig_index: int = -1 + del_lines: List[str] = field(default_factory=list) + ins_lines: List[str] = field(default_factory=list) + + +@dataclass +class PatchAction: + type: ActionType + new_file: Optional[str] = None + chunks: List[Chunk] = field(default_factory=list) + move_path: Optional[str] = None + + +@dataclass +class Patch: + actions: Dict[str, PatchAction] = field(default_factory=dict) \ No newline at end of file diff --git a/codetide/mcp/tools/openai_appy_patch/parser.py b/codetide/mcp/tools/openai_appy_patch/parser.py new file mode 100644 index 0000000..0fd8863 --- /dev/null +++ b/codetide/mcp/tools/openai_appy_patch/parser.py @@ -0,0 +1,170 @@ +from .models import Patch, DiffError, PatchAction, ActionType +from .utils import peek_next_section, find_context + +from typing import Dict, List, Optional, Tuple, Union +from dataclasses import dataclass, field + +# --------------------------------------------------------------------------- # +# Patch text parser +# --------------------------------------------------------------------------- # +@dataclass +class Parser: + current_files: Dict[str, str] + lines: List[str] + index: int = 0 + patch: Patch = field(default_factory=Patch) + fuzz: int = 0 + + # ------------- low-level helpers -------------------------------------- # + def _cur_line(self) -> str: + if self.index >= len(self.lines): + raise DiffError("Unexpected end of input while parsing patch") + return self.lines[self.index] + + @staticmethod + def _norm(line: str) -> str: + """Strip CR so comparisons work for both LF and CRLF input.""" + return line.rstrip("\r") + + # ------------- scanning convenience ----------------------------------- # + def is_done(self, prefixes: Optional[Tuple[str, ...]] = None) -> bool: + if self.index >= len(self.lines): + return True + if ( + prefixes + and len(prefixes) > 0 + and self._norm(self._cur_line()).startswith(prefixes) + ): + return True + return False + + def startswith(self, prefix: Union[str, Tuple[str, ...]]) -> bool: + return self._norm(self._cur_line()).startswith(prefix) + + def read_str(self, prefix: str) -> str: + """ + Consume the current line if it starts with *prefix* and return the text + **after** the prefix. Raises if prefix is empty. + """ + if prefix == "": + raise ValueError("read_str() requires a non-empty prefix") + if self._norm(self._cur_line()).startswith(prefix): + text = self._cur_line()[len(prefix) :] + self.index += 1 + return text + return "" + + def read_line(self) -> str: + """Return the current raw line and advance.""" + line = self._cur_line() + self.index += 1 + return line + + # ------------- public entry point -------------------------------------- # + def parse(self) -> None: + while not self.is_done(("*** End Patch",)): + # ---------- UPDATE ---------- # + path = self.read_str("*** Update File: ") + if path: + if path in self.patch.actions: + raise DiffError(f"Duplicate update for file: {path}") + move_to = self.read_str("*** Move to: ") + if path not in self.current_files: + raise DiffError(f"Update File Error - missing file: {path}") + text = self.current_files[path] + action = self._parse_update_file(text) + action.move_path = move_to or None + self.patch.actions[path] = action + continue + + # ---------- DELETE ---------- # + path = self.read_str("*** Delete File: ") + if path: + if path in self.patch.actions: + raise DiffError(f"Duplicate delete for file: {path}") + if path not in self.current_files: + raise DiffError(f"Delete File Error - missing file: {path}") + self.patch.actions[path] = PatchAction(type=ActionType.DELETE) + continue + + # ---------- ADD ---------- # + path = self.read_str("*** Add File: ") + if path: + if path in self.patch.actions: + raise DiffError(f"Duplicate add for file: {path}") + if path in self.current_files: + raise DiffError(f"Add File Error - file already exists: {path}") + self.patch.actions[path] = self._parse_add_file() + continue + + raise DiffError(f"Unknown line while parsing: {self._cur_line()}") + + if not self.startswith("*** End Patch"): + raise DiffError("Missing *** End Patch sentinel") + self.index += 1 # consume sentinel + + # ------------- section parsers ---------------------------------------- # + def _parse_update_file(self, text: str) -> PatchAction: + action = PatchAction(type=ActionType.UPDATE) + lines = text.split("\n") + index = 0 + while not self.is_done( + ( + "*** End Patch", + "*** Update File:", + "*** Delete File:", + "*** Add File:", + "*** End of File", + ) + ): + def_str = self.read_str("@@ ") + section_str = "" + if not def_str and self._norm(self._cur_line()) == "@@": + section_str = self.read_line() + + if not (def_str or section_str or index == 0): + raise DiffError(f"Invalid line in update section:\n{self._cur_line()}") + + if def_str.strip(): + found = False + if def_str not in lines[:index]: + for i, s in enumerate(lines[index:], index): + if s == def_str: + index = i + 1 + found = True + break + if not found and def_str.strip() not in [ + s.strip() for s in lines[:index] + ]: + for i, s in enumerate(lines[index:], index): + if s.strip() == def_str.strip(): + index = i + 1 + self.fuzz += 1 + found = True + break + + next_ctx, chunks, end_idx, eof = peek_next_section(self.lines, self.index) + new_index, fuzz = find_context(lines, next_ctx, index, eof) + if new_index == -1: + ctx_txt = "\n".join(next_ctx) + raise DiffError( + f"Invalid {'EOF ' if eof else ''}context at {index}:\n{ctx_txt}" + ) + self.fuzz += fuzz + for ch in chunks: + ch.orig_index += new_index + action.chunks.append(ch) + index = new_index + len(next_ctx) + self.index = end_idx + return action + + def _parse_add_file(self) -> PatchAction: + lines: List[str] = [] + while not self.is_done( + ("*** End Patch", "*** Update File:", "*** Delete File:", "*** Add File:") + ): + s = self.read_line() + if not s.startswith("+"): + raise DiffError(f"Invalid Add File line (missing '+'): {s}") + lines.append(s[1:]) # strip leading '+' + return PatchAction(type=ActionType.ADD, new_file="\n".join(lines)) diff --git a/codetide/mcp/tools/openai_appy_patch/utils.py b/codetide/mcp/tools/openai_appy_patch/utils.py new file mode 100644 index 0000000..b93ba92 --- /dev/null +++ b/codetide/mcp/tools/openai_appy_patch/utils.py @@ -0,0 +1,167 @@ +from .models import Chunk, DiffError, PatchAction, Patch, ActionType, FileChange, Commit +from typing import List, Tuple, Dict + +# --------------------------------------------------------------------------- # +# Helper functions +# --------------------------------------------------------------------------- # +def find_context_core( + lines: List[str], context: List[str], start: int +) -> Tuple[int, int]: + if not context: + return start, 0 + + for i in range(start, len(lines)): + if lines[i : i + len(context)] == context: + return i, 0 + for i in range(start, len(lines)): + if [s.rstrip() for s in lines[i : i + len(context)]] == [ + s.rstrip() for s in context + ]: + return i, 1 + for i in range(start, len(lines)): + if [s.strip() for s in lines[i : i + len(context)]] == [ + s.strip() for s in context + ]: + return i, 100 + return -1, 0 + +def find_context( + lines: List[str], context: List[str], start: int, eof: bool +) -> Tuple[int, int]: + if eof: + new_index, fuzz = find_context_core(lines, context, len(lines) - len(context)) + if new_index != -1: + return new_index, fuzz + new_index, fuzz = find_context_core(lines, context, start) + return new_index, fuzz + 10_000 + return find_context_core(lines, context, start) + +def peek_next_section( + lines: List[str], index: int +) -> Tuple[List[str], List[Chunk], int, bool]: + old: List[str] = [] + del_lines: List[str] = [] + ins_lines: List[str] = [] + chunks: List[Chunk] = [] + mode = "keep" + orig_index = index + + while index < len(lines): + s = lines[index] + if s.startswith( + ( + "@@", + "*** End Patch", + "*** Update File:", + "*** Delete File:", + "*** Add File:", + "*** End of File", + ) + ): + break + if s == "***": + break + if s.startswith("***"): + raise DiffError(f"Invalid Line: {s}") + index += 1 + + last_mode = mode + if s == "": + s = " " + if s[0] == "+": + mode = "add" + elif s[0] == "-": + mode = "delete" + elif s[0] == " ": + mode = "keep" + else: + raise DiffError(f"Invalid Line: {s}") + s = s[1:] + + if mode == "keep" and last_mode != mode: + if ins_lines or del_lines: + chunks.append( + Chunk( + orig_index=len(old) - len(del_lines), + del_lines=del_lines, + ins_lines=ins_lines, + ) + ) + del_lines, ins_lines = [], [] + + if mode == "delete": + del_lines.append(s) + old.append(s) + elif mode == "add": + ins_lines.append(s) + elif mode == "keep": + old.append(s) + + if ins_lines or del_lines: + chunks.append( + Chunk( + orig_index=len(old) - len(del_lines), + del_lines=del_lines, + ins_lines=ins_lines, + ) + ) + + if index < len(lines) and lines[index] == "*** End of File": + index += 1 + return old, chunks, index, True + + if index == orig_index: + raise DiffError("Nothing in this section") + return old, chunks, index, False + +# --------------------------------------------------------------------------- # +# Patch → Commit and Commit application +# --------------------------------------------------------------------------- # +def _get_updated_file(text: str, action: PatchAction, path: str) -> str: + if action.type is not ActionType.UPDATE: + raise DiffError("_get_updated_file called with non-update action") + orig_lines = text.split("\n") + dest_lines: List[str] = [] + orig_index = 0 + + for chunk in action.chunks: + if chunk.orig_index > len(orig_lines): + raise DiffError( + f"{path}: chunk.orig_index {chunk.orig_index} exceeds file length" + ) + if orig_index > chunk.orig_index: + raise DiffError( + f"{path}: overlapping chunks at {orig_index} > {chunk.orig_index}" + ) + + dest_lines.extend(orig_lines[orig_index : chunk.orig_index]) + orig_index = chunk.orig_index + + dest_lines.extend(chunk.ins_lines) + orig_index += len(chunk.del_lines) + + dest_lines.extend(orig_lines[orig_index:]) + return "\n".join(dest_lines) + +def patch_to_commit(patch: Patch, orig: Dict[str, str]) -> Commit: + commit = Commit() + for path, action in patch.actions.items(): + if action.type is ActionType.DELETE: + commit.changes[path] = FileChange( + type=ActionType.DELETE, old_content=orig[path] + ) + elif action.type is ActionType.ADD: + if action.new_file is None: + raise DiffError("ADD action without file content") + commit.changes[path] = FileChange( + type=ActionType.ADD, new_content=action.new_file + ) + elif action.type is ActionType.UPDATE: + new_content = _get_updated_file(orig[path], action, path) + commit.changes[path] = FileChange( + type=ActionType.UPDATE, + old_content=orig[path], + new_content=new_content, + move_path=action.move_path, + ) + return commit From cc8f7213c3bfc3a870d9e92e55496afd1b88c7f0 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Tue, 1 Jul 2025 23:41:55 +0100 Subject: [PATCH 23/88] Add applyPatch to exports in __init__.py for tools and mcp modules --- codetide/mcp/__init__.py | 5 +++-- codetide/mcp/tools/__init__.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/codetide/mcp/__init__.py b/codetide/mcp/__init__.py index 7407264..9d90653 100644 --- a/codetide/mcp/__init__.py +++ b/codetide/mcp/__init__.py @@ -1,9 +1,10 @@ from .server import codeTideMCPServer -from .tools import getContext, getRepoTree, checkCodeIdentifiers +from .tools import getContext, getRepoTree, checkCodeIdentifiers, applyPatch __all__ = [ "codeTideMCPServer", "getContext", "getRepoTree", - "checkCodeIdentifiers" + "checkCodeIdentifiers", + "applyPatch" ] \ No newline at end of file diff --git a/codetide/mcp/tools/__init__.py b/codetide/mcp/tools/__init__.py index 36a4e3a..3b10415 100644 --- a/codetide/mcp/tools/__init__.py +++ b/codetide/mcp/tools/__init__.py @@ -1,9 +1,11 @@ from .get_context import getContext from .get_repo_tree import getRepoTree from .check_code_identifiers import checkCodeIdentifiers +from .apply_patch import applyPatch __all__ = [ "getContext", "getRepoTree", - "checkCodeIdentifiers" + "checkCodeIdentifiers", + "applyPatch" ] \ No newline at end of file From 4adfe93ff9ed8e9c9b47b929d8cf983411132383 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Wed, 2 Jul 2025 22:42:29 +0100 Subject: [PATCH 24/88] Refactor getContext function to enforce list type for code_identifiers and enhance docstring clarity --- codetide/mcp/tools/get_context.py | 43 +++++++++++++++++-------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/codetide/mcp/tools/get_context.py b/codetide/mcp/tools/get_context.py index e3489f6..c483178 100644 --- a/codetide/mcp/tools/get_context.py +++ b/codetide/mcp/tools/get_context.py @@ -1,42 +1,47 @@ -from typing import List, Union from ..server import codeTideMCPServer from ..utils import initCodeTide +from typing import List + @codeTideMCPServer.tool async def getContext( - code_identifiers: Union[str, List[str]], + code_identifiers: List[str], context_depth: int = 1) -> str: """ - Retrieves code context for one or more identifiers. Returns None if invalid. + Retrieve code context for one or more identifiers. Args: - code_identifiers: Dot- or slash-notated identifiers, such as: + code_identifiers: List of dot- or slash-form identifiers, e.g.: + - 'package.module.Class.method' - 'tests.module.TestClass.test_method' - - 'package.module.Class.method_or_attr' - - 'services.backend.Service.execute' - 'package/module.py' - - Or a list of such identifiers + Prefer batching multiple identifiers in a single call for better + cross-referencing and performance. - context_depth: Reference depth to include: - 0 – Only the requested element(s) - 1 – Direct references (default) - 2+ – Recursive reference levels + context_depth (int): Reference depth: + 0 – Only requested elements + 1 – + Direct references (default) + 2+ – + Recursive references Returns: - A formatted string with: + str: Formatted context including: - Declarations - Imports - Related references - Syntax-highlighted code - Returns None if identifiers are not found. + Returns None if no matching identifiers found. - Notes: + Guidelines: - Identifiers are case-sensitive - - Use full paths for test elements and include 'test_' prefix - - Setters: Use @property.setter format - - Private methods: Include leading underscore - - File paths must be forward slashes, repo-relative - - Use getRepoTree() to explore available identifiers + - Use full test paths with `test_` prefix + - For setters: use @property.setter format + - Include underscores for private members + - File paths must use forward slashes, repo-relative + - Use getRepoTree() to discover valid identifiers + + Note: + To analyze related code, retrieve all needed identifiers in a single call to + getContext. This ensures better performance and richer, linked context. """ tide = await initCodeTide() From f78806a2247f32247f3c9e6c81aa299756f0ebe2 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Wed, 2 Jul 2025 22:43:07 +0100 Subject: [PATCH 25/88] Refactor patch handling: consolidate patch processing logic into new patch_code module, enhance error handling, and improve parser functionality --- codetide/mcp/tools/apply_patch.py | 121 +++--- .../mcp/tools/openai_appy_patch/__init__.py | 106 ------ .../mcp/tools/openai_appy_patch/parser.py | 170 --------- codetide/mcp/tools/openai_appy_patch/utils.py | 167 --------- codetide/mcp/tools/patch_code/__init__.py | 134 +++++++ .../models.py | 5 + codetide/mcp/tools/patch_code/parser.py | 349 ++++++++++++++++++ 7 files changed, 559 insertions(+), 493 deletions(-) delete mode 100644 codetide/mcp/tools/openai_appy_patch/__init__.py delete mode 100644 codetide/mcp/tools/openai_appy_patch/parser.py delete mode 100644 codetide/mcp/tools/openai_appy_patch/utils.py create mode 100644 codetide/mcp/tools/patch_code/__init__.py rename codetide/mcp/tools/{openai_appy_patch => patch_code}/models.py (81%) create mode 100644 codetide/mcp/tools/patch_code/parser.py diff --git a/codetide/mcp/tools/apply_patch.py b/codetide/mcp/tools/apply_patch.py index f26eae3..55d4db1 100644 --- a/codetide/mcp/tools/apply_patch.py +++ b/codetide/mcp/tools/apply_patch.py @@ -1,67 +1,88 @@ -from .openai_appy_patch import process_patch, open_file, write_file, remove_file +from .patch_code import DiffError, file_exists, open_file, process_patch, remove_file, write_file from ..server import codeTideMCPServer from ..utils import initCodeTide @codeTideMCPServer.tool -async def applyPatch(input :str)->str: +async def applyPatch(patch_text: str) -> str: """ - This is a custom utility that makes it more convenient to add, remove, move, or edit code files. - `applyPatch` effectively allows you to execute a diff/patch against a file, but the format of the diff specification is unique to this task, so pay careful attention to these instructions. - To use the `applyPatch` command, you should pass a message of the following structure as "input": + Apply structured patches to the filesystem. - %%bash - applyPatch <<"EOF" - *** Begin Patch - [YOUR_PATCH] - *** End Patch - EOF + Patch Format: + - Wrap in: *** Begin Patch ... *** End Patch + - Supports: Add, Update, Delete, Move (via Update + Move to) - Where [YOUR_PATCH] is the actual content of your patch, specified in the following V4A diff format. + Syntax: + - Context lines: ' ' + - Removed lines: '-' + - Added lines: '+' + - Change blocks: start with '@@' + - Use '*** End of File' to update file endings + - Relative paths only - *** [ACTION] File: [path/to/file] -> ACTION can be one of Add, Update, or Delete. - For each snippet of code that needs to be changed, repeat the following: - [context_before] -> See below for further instructions on context. - - [old_code] -> Precede the old code with a minus sign. - + [new_code] -> Precede the new, replacement code with a plus sign. - [context_after] -> See below for further instructions on context. + Key Features: + - Fuzzy matching, CRLF normalization + - Multiple hunks per file + - Atomic: all or nothing + - Validates file existence (no overwrites on Add) - For instructions on [context_before] and [context_after]: - - By default, show 3 lines of code immediately above and 3 lines immediately below each change. If a change is within 3 lines of a previous change, do NOT duplicate the first change’s [context_after] lines in the second change’s [context_before] lines. - - If 3 lines of context is insufficient to uniquely identify the snippet of code within the file, use the @@ operator to indicate the class or function to which the snippet belongs. For instance, we might have: - @@ class BaseClass - [3 lines of pre-context] - - [old_code] - + [new_code] - [3 lines of post-context] + Errors: + - FileNotFoundError: missing file for Update/Delete + - DiffError: bad format, context mismatch, or conflict + - DiffError: Add/Move to already-existing file - - If a code block is repeated so many times in a class or function such that even a single @@ statement and 3 lines of context cannot uniquely identify the snippet of code, you can use multiple `@@` statements to jump to the right context. For instance: + LLM Tips: + 1. Include 2-3 context lines around changes + 2. Preserve indentation/whitespace + 3. Prefer small, isolated patches + 4. Add full content for new files (with imports/defs) + 5. Ensure move destinations exist or are creatable - @@ class BaseClass - @@ def method(): - [3 lines of pre-context] - - [old_code] - + [new_code] - [3 lines of post-context] - - Note, then, that we do not use line numbers in this diff format, as the context is enough to uniquely identify code. An example of a message that you might pass as "input" to this function, in order to apply a patch, is shown below. + Examples: + # Add file + *** Begin Patch + *** Add File: new.py + +print("Hello") + *** End Patch - %%bash - applyPatch <<"EOF" + # Update file *** Begin Patch - *** Update File: pygorithm/searching/binary_search.py - @@ class BaseClass - @@ def search(): - - pass - + raise NotImplementedError() + *** Update File: main.py + @@ def greet(): + -print("Hi") + +print("Hello") + *** End Patch - @@ class Subclass - @@ def search(): - - pass - + raise NotImplementedError() + # Delete file + *** Begin Patch + *** Delete File: old.py + *** End Patch + # Move with update + *** Begin Patch + *** Update File: a.py + *** Move to: b.py + @@ def f(): + -print("x") + +print("y") + *** End of File *** End Patch - EOF """ - result = process_patch(input, open_file, write_file, remove_file) - _ = await initCodeTide() - return result + + try: + print(f"{patch_text=}") + result = process_patch(patch_text, open_file, write_file, remove_file, file_exists) + _ = await initCodeTide() + + except DiffError as exc: + result = f"Error applying patch:\n{exc}" + raise exc + + except FileNotFoundError as exc: + result = f"Error: File not found - {exc}" + raise exc + + except Exception as exc: + result = f"An unexpected error occurred: {exc}" + raise exc + + return result \ No newline at end of file diff --git a/codetide/mcp/tools/openai_appy_patch/__init__.py b/codetide/mcp/tools/openai_appy_patch/__init__.py deleted file mode 100644 index be545b5..0000000 --- a/codetide/mcp/tools/openai_appy_patch/__init__.py +++ /dev/null @@ -1,106 +0,0 @@ -from .models import DiffError, Patch, Commit, ActionType -from .utils import patch_to_commit -from .parser import Parser - -from typing import Dict, Tuple, List, Callable -import pathlib - -# --------------------------------------------------------------------------- # -# User-facing helpers -# --------------------------------------------------------------------------- # -def text_to_patch(text: str, orig: Dict[str, str]) -> Tuple[Patch, int]: - lines = text.splitlines() # preserves blank lines, no strip() - if ( - len(lines) < 2 - or not Parser._norm(lines[0]).startswith("*** Begin Patch") - or Parser._norm(lines[-1]) != "*** End Patch" - ): - raise DiffError("Invalid patch text - missing sentinels") - - parser = Parser(current_files=orig, lines=lines, index=1) - parser.parse() - return parser.patch, parser.fuzz - - -def identify_files_needed(text: str) -> List[str]: - lines = text.splitlines() - return [ - line[len("*** Update File: ") :] - for line in lines - if line.startswith("*** Update File: ") - ] + [ - line[len("*** Delete File: ") :] - for line in lines - if line.startswith("*** Delete File: ") - ] - - -def identify_files_added(text: str) -> List[str]: - lines = text.splitlines() - return [ - line[len("*** Add File: ") :] - for line in lines - if line.startswith("*** Add File: ") - ] - -# --------------------------------------------------------------------------- # -# File-system helpers -# --------------------------------------------------------------------------- # -def load_files(paths: List[str], open_fn: Callable[[str], str]) -> Dict[str, str]: - return {path: open_fn(path) for path in paths} - - -def apply_commit( - commit: Commit, - write_fn: Callable[[str, str], None], - remove_fn: Callable[[str], None], -) -> None: - for path, change in commit.changes.items(): - if change.type is ActionType.DELETE: - remove_fn(path) - elif change.type is ActionType.ADD: - if change.new_content is None: - raise DiffError(f"ADD change for {path} has no content") - write_fn(path, change.new_content) - elif change.type is ActionType.UPDATE: - if change.new_content is None: - raise DiffError(f"UPDATE change for {path} has no new content") - target = change.move_path or path - write_fn(target, change.new_content) - if change.move_path: - remove_fn(path) - - -def process_patch( - text: str, - open_fn: Callable[[str], str], - write_fn: Callable[[str, str], None], - remove_fn: Callable[[str], None], -) -> str: - if not text.startswith("*** Begin Patch"): - raise DiffError("Patch text must start with *** Begin Patch") - paths = identify_files_needed(text) - orig = load_files(paths, open_fn) - patch, _fuzz = text_to_patch(text, orig) - commit = patch_to_commit(patch, orig) - apply_commit(commit, write_fn, remove_fn) - return "Done!" - - -# --------------------------------------------------------------------------- # -# Default FS helpers -# --------------------------------------------------------------------------- # -def open_file(path: str) -> str: - with open(path, "rt", encoding="utf-8") as fh: - return fh.read() - - -def write_file(path: str, content: str) -> None: - target = pathlib.Path(path) - target.parent.mkdir(parents=True, exist_ok=True) - with target.open("wt", encoding="utf-8") as fh: - fh.write(content) - - -def remove_file(path: str) -> None: - pathlib.Path(path).unlink(missing_ok=True) \ No newline at end of file diff --git a/codetide/mcp/tools/openai_appy_patch/parser.py b/codetide/mcp/tools/openai_appy_patch/parser.py deleted file mode 100644 index 0fd8863..0000000 --- a/codetide/mcp/tools/openai_appy_patch/parser.py +++ /dev/null @@ -1,170 +0,0 @@ -from .models import Patch, DiffError, PatchAction, ActionType -from .utils import peek_next_section, find_context - -from typing import Dict, List, Optional, Tuple, Union -from dataclasses import dataclass, field - -# --------------------------------------------------------------------------- # -# Patch text parser -# --------------------------------------------------------------------------- # -@dataclass -class Parser: - current_files: Dict[str, str] - lines: List[str] - index: int = 0 - patch: Patch = field(default_factory=Patch) - fuzz: int = 0 - - # ------------- low-level helpers -------------------------------------- # - def _cur_line(self) -> str: - if self.index >= len(self.lines): - raise DiffError("Unexpected end of input while parsing patch") - return self.lines[self.index] - - @staticmethod - def _norm(line: str) -> str: - """Strip CR so comparisons work for both LF and CRLF input.""" - return line.rstrip("\r") - - # ------------- scanning convenience ----------------------------------- # - def is_done(self, prefixes: Optional[Tuple[str, ...]] = None) -> bool: - if self.index >= len(self.lines): - return True - if ( - prefixes - and len(prefixes) > 0 - and self._norm(self._cur_line()).startswith(prefixes) - ): - return True - return False - - def startswith(self, prefix: Union[str, Tuple[str, ...]]) -> bool: - return self._norm(self._cur_line()).startswith(prefix) - - def read_str(self, prefix: str) -> str: - """ - Consume the current line if it starts with *prefix* and return the text - **after** the prefix. Raises if prefix is empty. - """ - if prefix == "": - raise ValueError("read_str() requires a non-empty prefix") - if self._norm(self._cur_line()).startswith(prefix): - text = self._cur_line()[len(prefix) :] - self.index += 1 - return text - return "" - - def read_line(self) -> str: - """Return the current raw line and advance.""" - line = self._cur_line() - self.index += 1 - return line - - # ------------- public entry point -------------------------------------- # - def parse(self) -> None: - while not self.is_done(("*** End Patch",)): - # ---------- UPDATE ---------- # - path = self.read_str("*** Update File: ") - if path: - if path in self.patch.actions: - raise DiffError(f"Duplicate update for file: {path}") - move_to = self.read_str("*** Move to: ") - if path not in self.current_files: - raise DiffError(f"Update File Error - missing file: {path}") - text = self.current_files[path] - action = self._parse_update_file(text) - action.move_path = move_to or None - self.patch.actions[path] = action - continue - - # ---------- DELETE ---------- # - path = self.read_str("*** Delete File: ") - if path: - if path in self.patch.actions: - raise DiffError(f"Duplicate delete for file: {path}") - if path not in self.current_files: - raise DiffError(f"Delete File Error - missing file: {path}") - self.patch.actions[path] = PatchAction(type=ActionType.DELETE) - continue - - # ---------- ADD ---------- # - path = self.read_str("*** Add File: ") - if path: - if path in self.patch.actions: - raise DiffError(f"Duplicate add for file: {path}") - if path in self.current_files: - raise DiffError(f"Add File Error - file already exists: {path}") - self.patch.actions[path] = self._parse_add_file() - continue - - raise DiffError(f"Unknown line while parsing: {self._cur_line()}") - - if not self.startswith("*** End Patch"): - raise DiffError("Missing *** End Patch sentinel") - self.index += 1 # consume sentinel - - # ------------- section parsers ---------------------------------------- # - def _parse_update_file(self, text: str) -> PatchAction: - action = PatchAction(type=ActionType.UPDATE) - lines = text.split("\n") - index = 0 - while not self.is_done( - ( - "*** End Patch", - "*** Update File:", - "*** Delete File:", - "*** Add File:", - "*** End of File", - ) - ): - def_str = self.read_str("@@ ") - section_str = "" - if not def_str and self._norm(self._cur_line()) == "@@": - section_str = self.read_line() - - if not (def_str or section_str or index == 0): - raise DiffError(f"Invalid line in update section:\n{self._cur_line()}") - - if def_str.strip(): - found = False - if def_str not in lines[:index]: - for i, s in enumerate(lines[index:], index): - if s == def_str: - index = i + 1 - found = True - break - if not found and def_str.strip() not in [ - s.strip() for s in lines[:index] - ]: - for i, s in enumerate(lines[index:], index): - if s.strip() == def_str.strip(): - index = i + 1 - self.fuzz += 1 - found = True - break - - next_ctx, chunks, end_idx, eof = peek_next_section(self.lines, self.index) - new_index, fuzz = find_context(lines, next_ctx, index, eof) - if new_index == -1: - ctx_txt = "\n".join(next_ctx) - raise DiffError( - f"Invalid {'EOF ' if eof else ''}context at {index}:\n{ctx_txt}" - ) - self.fuzz += fuzz - for ch in chunks: - ch.orig_index += new_index - action.chunks.append(ch) - index = new_index + len(next_ctx) - self.index = end_idx - return action - - def _parse_add_file(self) -> PatchAction: - lines: List[str] = [] - while not self.is_done( - ("*** End Patch", "*** Update File:", "*** Delete File:", "*** Add File:") - ): - s = self.read_line() - if not s.startswith("+"): - raise DiffError(f"Invalid Add File line (missing '+'): {s}") - lines.append(s[1:]) # strip leading '+' - return PatchAction(type=ActionType.ADD, new_file="\n".join(lines)) diff --git a/codetide/mcp/tools/openai_appy_patch/utils.py b/codetide/mcp/tools/openai_appy_patch/utils.py deleted file mode 100644 index b93ba92..0000000 --- a/codetide/mcp/tools/openai_appy_patch/utils.py +++ /dev/null @@ -1,167 +0,0 @@ -from .models import Chunk, DiffError, PatchAction, Patch, ActionType, FileChange, Commit -from typing import List, Tuple, Dict - -# --------------------------------------------------------------------------- # -# Helper functions -# --------------------------------------------------------------------------- # -def find_context_core( - lines: List[str], context: List[str], start: int -) -> Tuple[int, int]: - if not context: - return start, 0 - - for i in range(start, len(lines)): - if lines[i : i + len(context)] == context: - return i, 0 - for i in range(start, len(lines)): - if [s.rstrip() for s in lines[i : i + len(context)]] == [ - s.rstrip() for s in context - ]: - return i, 1 - for i in range(start, len(lines)): - if [s.strip() for s in lines[i : i + len(context)]] == [ - s.strip() for s in context - ]: - return i, 100 - return -1, 0 - -def find_context( - lines: List[str], context: List[str], start: int, eof: bool -) -> Tuple[int, int]: - if eof: - new_index, fuzz = find_context_core(lines, context, len(lines) - len(context)) - if new_index != -1: - return new_index, fuzz - new_index, fuzz = find_context_core(lines, context, start) - return new_index, fuzz + 10_000 - return find_context_core(lines, context, start) - -def peek_next_section( - lines: List[str], index: int -) -> Tuple[List[str], List[Chunk], int, bool]: - old: List[str] = [] - del_lines: List[str] = [] - ins_lines: List[str] = [] - chunks: List[Chunk] = [] - mode = "keep" - orig_index = index - - while index < len(lines): - s = lines[index] - if s.startswith( - ( - "@@", - "*** End Patch", - "*** Update File:", - "*** Delete File:", - "*** Add File:", - "*** End of File", - ) - ): - break - if s == "***": - break - if s.startswith("***"): - raise DiffError(f"Invalid Line: {s}") - index += 1 - - last_mode = mode - if s == "": - s = " " - if s[0] == "+": - mode = "add" - elif s[0] == "-": - mode = "delete" - elif s[0] == " ": - mode = "keep" - else: - raise DiffError(f"Invalid Line: {s}") - s = s[1:] - - if mode == "keep" and last_mode != mode: - if ins_lines or del_lines: - chunks.append( - Chunk( - orig_index=len(old) - len(del_lines), - del_lines=del_lines, - ins_lines=ins_lines, - ) - ) - del_lines, ins_lines = [], [] - - if mode == "delete": - del_lines.append(s) - old.append(s) - elif mode == "add": - ins_lines.append(s) - elif mode == "keep": - old.append(s) - - if ins_lines or del_lines: - chunks.append( - Chunk( - orig_index=len(old) - len(del_lines), - del_lines=del_lines, - ins_lines=ins_lines, - ) - ) - - if index < len(lines) and lines[index] == "*** End of File": - index += 1 - return old, chunks, index, True - - if index == orig_index: - raise DiffError("Nothing in this section") - return old, chunks, index, False - -# --------------------------------------------------------------------------- # -# Patch → Commit and Commit application -# --------------------------------------------------------------------------- # -def _get_updated_file(text: str, action: PatchAction, path: str) -> str: - if action.type is not ActionType.UPDATE: - raise DiffError("_get_updated_file called with non-update action") - orig_lines = text.split("\n") - dest_lines: List[str] = [] - orig_index = 0 - - for chunk in action.chunks: - if chunk.orig_index > len(orig_lines): - raise DiffError( - f"{path}: chunk.orig_index {chunk.orig_index} exceeds file length" - ) - if orig_index > chunk.orig_index: - raise DiffError( - f"{path}: overlapping chunks at {orig_index} > {chunk.orig_index}" - ) - - dest_lines.extend(orig_lines[orig_index : chunk.orig_index]) - orig_index = chunk.orig_index - - dest_lines.extend(chunk.ins_lines) - orig_index += len(chunk.del_lines) - - dest_lines.extend(orig_lines[orig_index:]) - return "\n".join(dest_lines) - -def patch_to_commit(patch: Patch, orig: Dict[str, str]) -> Commit: - commit = Commit() - for path, action in patch.actions.items(): - if action.type is ActionType.DELETE: - commit.changes[path] = FileChange( - type=ActionType.DELETE, old_content=orig[path] - ) - elif action.type is ActionType.ADD: - if action.new_file is None: - raise DiffError("ADD action without file content") - commit.changes[path] = FileChange( - type=ActionType.ADD, new_content=action.new_file - ) - elif action.type is ActionType.UPDATE: - new_content = _get_updated_file(orig[path], action, path) - commit.changes[path] = FileChange( - type=ActionType.UPDATE, - old_content=orig[path], - new_content=new_content, - move_path=action.move_path, - ) - return commit diff --git a/codetide/mcp/tools/patch_code/__init__.py b/codetide/mcp/tools/patch_code/__init__.py new file mode 100644 index 0000000..079aef7 --- /dev/null +++ b/codetide/mcp/tools/patch_code/__init__.py @@ -0,0 +1,134 @@ +from .models import DiffError, Patch, Commit, ActionType +from .parser import Parser, patch_to_commit + +from typing import Dict, Tuple, List, Callable +import pathlib + +# --------------------------------------------------------------------------- # +# User-facing API +# --------------------------------------------------------------------------- # +def text_to_patch(text: str, orig: Dict[str, str]) -> Tuple[Patch, int]: + """High-level function to parse patch text against original file content.""" + lines = text.splitlines() + if not lines or not Parser._norm(lines[0]).startswith("*** Begin Patch"): + raise DiffError("Invalid patch text - must start with '*** Begin Patch'.") + if not Parser._norm(lines[-1]) == "*** End Patch": + raise DiffError("Invalid patch text - must end with '*** End Patch'.") + + parser = Parser(current_files=orig, lines=lines, index=1) + parser.parse() + return parser.patch, parser.fuzz + + +def identify_files_needed(text: str) -> List[str]: + """Scans patch text to find which files need to be read.""" + lines = text.splitlines() + files = [] + for line in lines: + norm_line = Parser._norm(line) + if norm_line.startswith("*** Update File: "): + files.append(line[len("*** Update File: ") :]) + elif norm_line.startswith("*** Delete File: "): + files.append(line[len("*** Delete File: ") :]) + return files + + +def identify_files_added(text: str) -> List[str]: + """Scans patch text to find which files will be created.""" + lines = text.splitlines() + return [ + line[len("*** Add File: ") :] + for line in lines + if Parser._norm(line).startswith("*** Add File: ") + ] + + +# --------------------------------------------------------------------------- # +# File-system I/O +# --------------------------------------------------------------------------- # +def load_files(paths: List[str], open_fn: Callable[[str], str]) -> Dict[str, str]: + """Loads a list of files into a dictionary.""" + return {path: open_fn(path) for path in paths} + + +def apply_commit( + commit: Commit, + write_fn: Callable[[str, str], None], + remove_fn: Callable[[str], None], + exists_fn: Callable[[str], bool] +) -> None: + """Applies a commit to a filesystem using provided I/O functions.""" + # Check for move/rename collisions before applying any changes + for path, change in commit.changes.items(): + if change.move_path and exists_fn(change.move_path): + if not commit.changes.get(change.move_path, None): + raise DiffError(f"Cannot move '{path}' to '{change.move_path}' because the target file already exists.") + + for path, change in commit.changes.items(): + if change.type is ActionType.DELETE: + remove_fn(path) + elif change.type is ActionType.ADD: + if change.new_content is None: + raise DiffError(f"ADD change for '{path}' has no content") + write_fn(path, change.new_content) + elif change.type is ActionType.UPDATE: + if change.new_content is None: + raise DiffError(f"UPDATE change for '{path}' has no new content") + + target_path = change.move_path or path + write_fn(target_path, change.new_content) + + if change.move_path and target_path != path: + remove_fn(path) + + +def process_patch( + text: str, + open_fn: Callable[[str], str], + write_fn: Callable[[str, str], None], + remove_fn: Callable[[str], None], + exists_fn: Callable[[str], bool] +) -> str: + """The main entrypoint function to process a patch from text to filesystem.""" + if not text.strip(): + raise DiffError("Patch text is empty.") + + # FIX: Check for existence of files to be added before parsing. + paths_to_add = identify_files_added(text) + for p in paths_to_add: + if exists_fn(p): + raise DiffError(f"Add File Error - file already exists: {p}") + + paths_needed = identify_files_needed(text) + orig_files = load_files(paths_needed, open_fn) + + patch, _fuzz = text_to_patch(text, orig_files) + commit = patch_to_commit(patch, orig_files) + + apply_commit(commit, write_fn, remove_fn, exists_fn) + return "Patch applied successfully." + + +# --------------------------------------------------------------------------- # +# Default FS wrappers +# --------------------------------------------------------------------------- # +def open_file(path: str) -> str: + with open(path, "rt", encoding="utf-8") as fh: + return fh.read() + + +def write_file(path: str, content: str) -> None: + target = pathlib.Path(path) + target.parent.mkdir(parents=True, exist_ok=True) + with target.open("wt", encoding="utf-8", newline="\n") as fh: + fh.write(content) + + +def remove_file(path: str) -> None: + p = pathlib.Path(path) + if p.exists(): + p.unlink() + + +def file_exists(path: str) -> bool: + return pathlib.Path(path).exists() \ No newline at end of file diff --git a/codetide/mcp/tools/openai_appy_patch/models.py b/codetide/mcp/tools/patch_code/models.py similarity index 81% rename from codetide/mcp/tools/openai_appy_patch/models.py rename to codetide/mcp/tools/patch_code/models.py index ce375fb..c072936 100644 --- a/codetide/mcp/tools/openai_appy_patch/models.py +++ b/codetide/mcp/tools/patch_code/models.py @@ -19,6 +19,7 @@ class ActionType(str, Enum): @dataclass class FileChange: + """Represents a single, verified change to a file.""" type: ActionType old_content: Optional[str] = None new_content: Optional[str] = None @@ -27,6 +28,7 @@ class FileChange: @dataclass class Commit: + """Represents a collection of file changes to be applied.""" changes: Dict[str, FileChange] = field(default_factory=dict) @@ -42,6 +44,7 @@ class DiffError(ValueError): # --------------------------------------------------------------------------- # @dataclass class Chunk: + """Represents a single block of additions/deletions in an update.""" orig_index: int = -1 del_lines: List[str] = field(default_factory=list) ins_lines: List[str] = field(default_factory=list) @@ -49,6 +52,7 @@ class Chunk: @dataclass class PatchAction: + """Represents a parsed action (add, delete, update) from the patch text.""" type: ActionType new_file: Optional[str] = None chunks: List[Chunk] = field(default_factory=list) @@ -57,4 +61,5 @@ class PatchAction: @dataclass class Patch: + """Represents the entire parsed patch, with a dictionary of actions.""" actions: Dict[str, PatchAction] = field(default_factory=dict) \ No newline at end of file diff --git a/codetide/mcp/tools/patch_code/parser.py b/codetide/mcp/tools/patch_code/parser.py new file mode 100644 index 0000000..d43f276 --- /dev/null +++ b/codetide/mcp/tools/patch_code/parser.py @@ -0,0 +1,349 @@ +from .models import Patch, DiffError, PatchAction, ActionType, FileChange, Commit, Chunk + +from typing import Dict, List, Optional, Tuple, Union +from dataclasses import dataclass, field + +# --------------------------------------------------------------------------- # +# Helper functions for parsing update chunks +# --------------------------------------------------------------------------- # +def find_context_core( + lines: List[str], context: List[str], start: int +) -> Tuple[int, int]: + """Core fuzzy matching logic.""" + if not context: + return start, 0 + + # Exact match + for i in range(start, len(lines) - len(context) + 1): + if lines[i : i + len(context)] == context: + return i, 0 + + # Match ignoring trailing whitespace + norm_context = [s.rstrip() for s in context] + for i in range(start, len(lines) - len(context) + 1): + if [s.rstrip() for s in lines[i : i + len(context)]] == norm_context: + return i, 1 + + # Match ignoring all leading/trailing whitespace + strip_context = [s.strip() for s in context] + for i in range(start, len(lines) - len(context) + 1): + if [s.strip() for s in lines[i : i + len(context)]] == strip_context: + return i, 100 + + return -1, 0 + + +def find_context( + lines: List[str], context: List[str], start: int, eof: bool +) -> Tuple[int, int]: + """Finds the location of a context block, allowing for fuzziness.""" + if eof and context: + end_start_idx = len(lines) - len(context) + if end_start_idx >= 0: + new_index, fuzz = find_context_core(lines, context, end_start_idx) + if new_index == end_start_idx: + return new_index, fuzz + return -1, 0 + + return find_context_core(lines, context, start) + + +def peek_next_section( + lines: List[str], index: int +) -> Tuple[List[str], List[Chunk], int, bool]: + """ + Scans a block of changes within an 'Update File' section. + """ + context_lines: List[str] = [] + del_lines: List[str] = [] + ins_lines: List[str] = [] + chunks: List[Chunk] = [] + mode = "keep" + + while index < len(lines): + norm_s = Parser._norm(lines[index]) + if norm_s.startswith( + ( + "@@", + "*** End Patch", + "*** Update File:", + "*** Delete File:", + "*** Add File:", + "*** End of File", + ) + ): + break + + s = lines[index] + index += 1 + + last_mode = mode + prefix = s[0] if s else ' ' + + if prefix == "+": + mode = "add" + elif prefix == "-": + mode = "delete" + elif prefix == " ": + mode = "keep" + else: + raise DiffError(f"Invalid line in update section (must start with '+', '-', or ' '): {s}") + + content = s[1:] + + if mode == "keep" and last_mode != "keep": + if ins_lines or del_lines: + chunks.append( + Chunk( + orig_index=len(context_lines) - len(del_lines), + del_lines=del_lines, + ins_lines=ins_lines, + ) + ) + del_lines, ins_lines = [], [] + + if mode == "delete": + del_lines.append(content) + context_lines.append(content) + elif mode == "add": + ins_lines.append(content) + elif mode == "keep": + context_lines.append(content) + + if ins_lines or del_lines: + chunks.append( + Chunk( + orig_index=len(context_lines) - len(del_lines), + del_lines=del_lines, + ins_lines=ins_lines, + ) + ) + + is_eof = False + if index < len(lines) and Parser._norm(lines[index]) == "*** End of File": + index += 1 + is_eof = True + + return context_lines, chunks, index, is_eof + + +# --------------------------------------------------------------------------- # +# Patch → Commit and Commit application +# --------------------------------------------------------------------------- # +def _get_updated_file(text: str, action: PatchAction, path: str) -> str: + """Applies the chunks from a PatchAction to the original file content.""" + if action.type is not ActionType.UPDATE: + raise DiffError("_get_updated_file called with non-update action") + + orig_lines = text.splitlines() + dest_lines: List[str] = [] + orig_idx_ptr = 0 + + sorted_chunks = sorted(action.chunks, key=lambda c: c.orig_index) + + for chunk in sorted_chunks: + if chunk.orig_index > len(orig_lines): + raise DiffError( + f"In file '{path}', chunk tries to apply at line {chunk.orig_index + 1}, which is beyond the file's length of {len(orig_lines)} lines." + ) + if orig_idx_ptr > chunk.orig_index: + raise DiffError( + f"In file '{path}', detected overlapping chunks at line {chunk.orig_index + 1}." + ) + + dest_lines.extend(orig_lines[orig_idx_ptr : chunk.orig_index]) + dest_lines.extend(chunk.ins_lines) + orig_idx_ptr = chunk.orig_index + len(chunk.del_lines) + + dest_lines.extend(orig_lines[orig_idx_ptr:]) + + new_content = "\n".join(dest_lines) + if text.endswith('\n') or (not text and new_content): + new_content += '\n' + + return new_content + + +def patch_to_commit(patch: Patch, orig: Dict[str, str]) -> Commit: + """Converts a parsed Patch object into a Commit object with final content.""" + commit = Commit() + for path, action in patch.actions.items(): + if action.type is ActionType.DELETE: + commit.changes[path] = FileChange(type=ActionType.DELETE, old_content=orig[path]) + elif action.type is ActionType.ADD: + if action.new_file is None: + raise DiffError(f"ADD action for '{path}' has no content") + commit.changes[path] = FileChange(type=ActionType.ADD, new_content=action.new_file) + elif action.type is ActionType.UPDATE: + new_content = _get_updated_file(orig[path], action, path) + commit.changes[path] = FileChange( + type=ActionType.UPDATE, + old_content=orig[path], + new_content=new_content, + move_path=action.move_path, + ) + return commit + +# --------------------------------------------------------------------------- # +# Patch text parser +# --------------------------------------------------------------------------- # +@dataclass +class Parser: + """Parses patch text into a Patch object.""" + current_files: Dict[str, str] + lines: List[str] + index: int = 0 + patch: Patch = field(default_factory=Patch) + fuzz: int = 0 + + def _cur_line(self) -> str: + if self.index >= len(self.lines): + raise DiffError("Unexpected end of input while parsing patch") + return self.lines[self.index] + + @staticmethod + def _norm(line: str) -> str: + """Strip CR so comparisons work for both LF and CRLF input.""" + return line.rstrip("\r") + + def is_done(self, prefixes: Optional[Tuple[str, ...]] = None) -> bool: + if self.index >= len(self.lines): + return True + if prefixes and self._norm(self._cur_line()).startswith(prefixes): + return True + return False + + def startswith(self, prefix: Union[str, Tuple[str, ...]]) -> bool: + return self._norm(self._cur_line()).startswith(prefix) + + def read_str(self, prefix: str) -> str: + """Consumes the current line if it starts with *prefix* and returns the text after it.""" + if not prefix: + raise ValueError("read_str() requires a non-empty prefix") + norm_line = self._norm(self._cur_line()) + if norm_line.startswith(prefix): + text = self._cur_line()[len(prefix) :] + self.index += 1 + return text + return "" + + def read_line(self) -> str: + """Return the current raw line and advance.""" + line = self._cur_line() + self.index += 1 + return line + + def parse(self) -> None: + """Main parsing loop.""" + while not self.is_done(("*** End Patch",)): + # ---------- UPDATE ---------- # + if path := self.read_str("*** Update File: "): + if path in self.patch.actions: + raise DiffError(f"Duplicate update for file: {path}") + move_to = self.read_str("*** Move to: ") + if path not in self.current_files: + raise DiffError(f"Update File Error - file to be updated does not exist: {path}") + text = self.current_files[path] + action = self._parse_update_file(text, path) + action.move_path = move_to or None + self.patch.actions[path] = action + continue + + # ---------- DELETE ---------- # + if path := self.read_str("*** Delete File: "): + if path in self.patch.actions: + raise DiffError(f"Duplicate delete for file: {path}") + if path not in self.current_files: + raise DiffError(f"Delete File Error - file to be deleted does not exist: {path}") + self.patch.actions[path] = PatchAction(type=ActionType.DELETE) + continue + + # ---------- ADD ---------- # + if path := self.read_str("*** Add File: "): + if path in self.patch.actions: + raise DiffError(f"Duplicate add for file: {path}") + # The check for file existence is now handled in `process_patch` + self.patch.actions[path] = self._parse_add_file() + continue + + # If we are here, no known action was found + line_preview = self._cur_line().strip() + if not line_preview: # Skip blank lines between actions + self.index += 1 + continue + raise DiffError(f"Unknown or malformed action line while parsing: '{line_preview}'") + + if not self.startswith("*** End Patch"): + raise DiffError("Missing '*** End Patch' sentinel at the end of the file") + self.index += 1 # consume sentinel + + def _parse_update_file(self, text: str, path_for_error: str) -> PatchAction: + """Parses the content of an 'Update File' section.""" + action = PatchAction(type=ActionType.UPDATE) + orig_lines = text.splitlines() + + first_ctx_line = self.read_str("@@ ").strip() + search_start_idx = 0 + if first_ctx_line: + # FIX: Use fuzzy finding for the initial context line + idx, _fuzz = find_context_core(orig_lines, [first_ctx_line], 0) + if idx == -1: + raise DiffError(f"In file '{path_for_error}', could not find initial context line: '{first_ctx_line}'") + # FIX: The search for the full context block should start at the found line, not after it. + search_start_idx = idx + + while not self.is_done( + ( + "*** End Patch", + "*** Update File:", + "*** Delete File:", + "*** Add File:", + ) + ): + next_ctx, chunks, end_idx, eof = peek_next_section(self.lines, self.index) + if not chunks: + self.index = end_idx + if self.startswith("@@"): # Consume the marker for the next section + self.index += 1 + if eof or self.is_done( # If no more chunks, we might be done + ("*** End Patch", "*** Update File:", "*** Delete File:", "*** Add File:") + ): + break + continue + + new_index, fuzz = find_context(orig_lines, next_ctx, search_start_idx, eof) + if new_index == -1: + ctx_txt = "\n".join(next_ctx) + raise DiffError( + f"In file '{path_for_error}', could not find context block:\n---\n{ctx_txt}\n---" + ) + + self.fuzz += fuzz + for ch in chunks: + ch.orig_index += new_index + action.chunks.append(ch) + + search_start_idx = new_index + len(next_ctx) + self.index = end_idx + + return action + + def _parse_add_file(self) -> PatchAction: + """Parses the content of an 'Add File' section.""" + lines: List[str] = [] + # FIX: Loop robustly, stopping when a line is not part of the add block. + while not self.is_done(("*** End Patch", "*** Update File:", "*** Delete File:", "*** Add File:")): + current_line = self._cur_line() + if not current_line.startswith('+'): + # Line is not part of the add block, so stop parsing this section. + break + + s = self.read_line() # Consume the line + lines.append(s[1:]) + + content = "\n".join(lines) + # FIX: By convention, ensure non-empty text files end with a newline. + if content: + content += '\n' + + return PatchAction(type=ActionType.ADD, new_file=content) \ No newline at end of file From 7787fe8089bd8224968007c071dd8691790a141f Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Wed, 2 Jul 2025 22:43:20 +0100 Subject: [PATCH 26/88] Add comprehensive tests for patch application functionality in MockFileSystem --- tests/mcp/tools/test_apply_patch.py | 327 ++++++++++++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 tests/mcp/tools/test_apply_patch.py diff --git a/tests/mcp/tools/test_apply_patch.py b/tests/mcp/tools/test_apply_patch.py new file mode 100644 index 0000000..54a2b43 --- /dev/null +++ b/tests/mcp/tools/test_apply_patch.py @@ -0,0 +1,327 @@ +from codetide.mcp.tools.apply_patch import ( + DiffError, + process_patch +) + +from typing import Dict +import pytest +import re + +class MockFileSystem: + """Mock filesystem for testing.""" + + def __init__(self): + self.fs: Dict[str, str] = {} + # Pre-populate with some files + self.fs['main.py'] = ( + "def hello():\n" + " print('Hello, world!')\n" + "\n" + "def goodbye():\n" + " print('Goodbye, world!')\n" + "\n" + "if __name__ == '__main__':\n" + " hello()\n" + ) + self.fs['data/old_data.txt'] = "line1\nline2\nline3\n" + self.fs['empty.txt'] = "" + self.fs['utils.py'] = "# A utility file.\n" + self.fs['fuzzy.py'] = " def my_func( a, b ): \n return a+b\n" + + def mock_open(self, path: str) -> str: + if path not in self.fs: + raise FileNotFoundError(f"File not found in mock filesystem: {path}") + return self.fs[path] + + def mock_write(self, path: str, content: str) -> None: + self.fs[path] = content + + def mock_remove(self, path: str) -> None: + if path in self.fs: + del self.fs[path] + + def mock_exists(self, path: str) -> bool: + return path in self.fs + + def apply_patch(self, patch_text: str): + """Helper to run the whole process against the mock fs.""" + return process_patch( + patch_text, + self.mock_open, + self.mock_write, + self.mock_remove, + self.mock_exists, + ) + + +@pytest.fixture +def mock_fs(): + """Fixture providing a fresh mock filesystem for each test.""" + return MockFileSystem() + + +def test_add_file(mock_fs): + patch = """*** Begin Patch +*** Add File: new_module.py ++import os ++ ++def new_function(): ++ return os.getcwd() +*** End Patch +""" + mock_fs.apply_patch(patch) + assert 'new_module.py' in mock_fs.fs + expected_content = "import os\n\ndef new_function():\n return os.getcwd()\n" + assert mock_fs.fs['new_module.py'] == expected_content + + +def test_delete_file(mock_fs): + assert 'utils.py' in mock_fs.fs + patch = """*** Begin Patch +*** Delete File: utils.py +*** End Patch +""" + mock_fs.apply_patch(patch) + assert 'utils.py' not in mock_fs.fs + + +def test_update_middle_of_file(mock_fs): + patch = """*** Begin Patch +*** Update File: main.py +@@ def goodbye(): +- print('Goodbye, world!') ++ # A new and improved goodbye ++ print('Farewell, cruel world!') + + if __name__ == '__main__': +*** End Patch +""" + mock_fs.apply_patch(patch) + assert "Farewell, cruel world!" in mock_fs.fs['main.py'] + assert "Goodbye, world!" not in mock_fs.fs['main.py'] + + +def test_update_start_of_file(mock_fs): + patch = """*** Begin Patch +*** Update File: main.py +@@ def hello(): +-def hello(): +- print('Hello, world!') ++def new_hello(): ++ print('Greetings, planet!') + + def goodbye(): +*** End Patch +""" + mock_fs.apply_patch(patch) + assert "Greetings, planet!" in mock_fs.fs['main.py'] + assert "Hello, world!" not in mock_fs.fs['main.py'] + expected = ( + "def new_hello():\n" + " print('Greetings, planet!')\n" + "\n" + "def goodbye():\n" + " print('Goodbye, world!')\n" + "\n" + "if __name__ == '__main__':\n" + " hello()\n" + ) + assert mock_fs.fs['main.py'] == expected + + +def test_update_end_of_file_marker(mock_fs): + patch = """*** Begin Patch +*** Update File: main.py +@@ if __name__ == '__main__': +- hello() ++ # A new entrypoint ++ goodbye() +*** End of File +*** End Patch +""" + mock_fs.apply_patch(patch) + assert "goodbye()" in mock_fs.fs['main.py'] + assert " hello()" not in mock_fs.fs['main.py'] + assert mock_fs.fs['main.py'].endswith("goodbye()\n") + + +def test_rename_file(mock_fs): + patch = """*** Begin Patch +*** Update File: data/old_data.txt +*** Move to: data/new_data.txt +@@ line1 + line1 +-line2 ++line_two + line3 +*** End of File +*** End Patch +""" + mock_fs.apply_patch(patch) + assert 'data/old_data.txt' not in mock_fs.fs + assert 'data/new_data.txt' in mock_fs.fs + assert mock_fs.fs['data/new_data.txt'] == "line1\nline_two\nline3\n" + + +def test_crlf_handling(mock_fs): + mock_fs.fs['crlf.txt'] = "line one\r\nline two\r\nline three\r\n" + patch = """*** Begin Patch +*** Update File: crlf.txt +@@ line one + line one +-line two ++line 2 + line three +*** End of File +*** End Patch +""" + mock_fs.apply_patch(patch) + assert mock_fs.fs['crlf.txt'] == "line one\nline 2\nline three\n" + + +def test_fuzzy_matching_whitespace(mock_fs): + # The patch context has different surrounding whitespace than the original file + patch = """*** Begin Patch +*** Update File: fuzzy.py +@@ def my_func( a, b ): +- return a+b ++ return a * b # Now we multiply +*** End of File +*** End Patch +""" + mock_fs.apply_patch(patch) + assert "return a * b" in mock_fs.fs['fuzzy.py'] + + +def test_full_add_update_delete_patch(mock_fs): + patch = """*** Begin Patch +*** Add File: new_feature.py ++def feature(): ++ print("New!") + +*** Update File: main.py +@@ if __name__ == '__main__': + if __name__ == '__main__': +- hello() ++ print("Running main") +*** Delete File: utils.py +*** End Patch +""" + mock_fs.apply_patch(patch) + assert 'new_feature.py' in mock_fs.fs + assert 'utils.py' not in mock_fs.fs + assert 'Running main' in mock_fs.fs['main.py'] + # FIX: Make assertion more specific. Check that the *call* was removed, + # not that the substring 'hello()' is gone from the entire file. + assert ' hello()' not in mock_fs.fs['main.py'] + + +# --- Error Condition Tests --- + +def test_error_missing_sentinels(mock_fs): + with pytest.raises(DiffError, match=r"must start with '\*\*\* Begin Patch'"): + mock_fs.apply_patch("just some text\n*** End Patch\n") + + with pytest.raises(DiffError, match=r"must end with '\*\*\* End Patch'"): + mock_fs.apply_patch("*** Begin Patch\n*** Add File: a.txt") + + +def test_error_update_nonexistent_file(mock_fs): + patch = """*** Begin Patch +*** Update File: no_such_file.py +*** End Patch +""" + with pytest.raises(FileNotFoundError): + mock_fs.apply_patch(patch) + + +def test_error_delete_nonexistent_file(mock_fs): + patch = """*** Begin Patch +*** Delete File: no_such_file.py +*** End Patch +""" + with pytest.raises(FileNotFoundError): + mock_fs.apply_patch(patch) + + +def test_error_add_existing_file(mock_fs): + patch = """*** Begin Patch +*** Add File: main.py ++print("overwrite!") +*** End Patch +""" + with pytest.raises(DiffError, match="file already exists: main.py"): + mock_fs.apply_patch(patch) + + +def test_error_duplicate_action(mock_fs): + patch = """*** Begin Patch +*** Delete File: utils.py +*** Delete File: utils.py +*** End Patch +""" + with pytest.raises(DiffError, match="Duplicate delete for file: utils.py"): + mock_fs.apply_patch(patch) + + +def test_error_context_not_found(mock_fs): + patch = """*** Begin Patch +*** Update File: main.py +@@ some nonexistent context +-foo ++bar +*** End Patch +""" + with pytest.raises(DiffError, match="could not find initial context line"): + mock_fs.apply_patch(patch) + + +def test_error_second_context_not_found(mock_fs): + patch = """*** Begin Patch +*** Update File: main.py +@@ def hello(): +- print('Hello, world!') ++ print('Hello, new world!') +@@ this context does not exist +-goodbye ++farewell +*** End Patch +""" + with pytest.raises(DiffError, match=re.escape("could not find context block")): + mock_fs.apply_patch(patch) + + +def test_error_move_to_existing_file(mock_fs): + patch = """*** Begin Patch +*** Update File: utils.py +*** Move to: main.py +@@ # A utility file. +-# A utility file. ++# Overwrite main! +*** End of File +*** End Patch +""" + with pytest.raises(DiffError, match="Cannot move 'utils.py' to 'main.py' because the target file already exists"): + mock_fs.apply_patch(patch) + + +def test_error_invalid_line_in_add(mock_fs): + patch = """*** Begin Patch +*** Add File: a.txt + this line is invalid ++but this one is ok +*** End Patch +""" + with pytest.raises(DiffError, match=r"Unknown or malformed action line"): + mock_fs.apply_patch(patch) + + +def test_error_invalid_line_in_update(mock_fs): + patch = """*** Begin Patch +*** Update File: main.py +@@ def hello(): +this line is invalid +*** End Patch +""" + with pytest.raises(DiffError, match=r"must start with '\+', '-', or ' '"): + mock_fs.apply_patch(patch) \ No newline at end of file From 4fe6d8ae50d2dc60c2a4734990d70fe49c249239 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Wed, 2 Jul 2025 22:43:34 +0100 Subject: [PATCH 27/88] Add MCP server support to README, detailing integration with AI agents and available tools --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/README.md b/README.md index 283fae9..b96679e 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,53 @@ CodeTide is available as a native [**Visual Studio Code extension**](https://mar --- +## 🖧 CodeTide as an MCP Server + +CodeTide now supports acting as an **MCP (Multi-Codebase Processing) Server**, enabling seamless integration with AI agents and tools. This feature allows agents to dynamically interact with your codebase, retrieve context, and apply changes efficiently. + +#### Why This Helps Agents +Agents working with codebases often need: +- **Contextual Understanding**: Retrieve declarations, imports, and references for any part of the code. +- **Tool Integration**: Use built-in tools to navigate, modify, and validate code. +- **Atomic Operations**: Apply patches or updates without manual intervention. + +#### Available Tools +CodeTide provides the following tools for agents: +1. **`applyPatch`**: Apply structured patches to files. +2. **`getContext`**: Retrieve code context for identifiers (e.g., functions, classes). +3. **`getRepoTree`**: Explore the repository structure. +4. **`checkCodeIdentifiers`**: Validate and suggest corrections for code identifiers. + +#### Example: Initializing an LLM with CodeTide +Here’s a snippet from `agent_tide.py` demonstrating how to initialize an LLM with CodeTide as an MCP server: + +```python +from aicore.llm import Llm, LlmConfig +from codetide.mcp import codeTideMCPServer +import os + +def init_llm() -> Llm: + llm = Llm.from_config( + LlmConfig( + model="deepseek-chat", + provider="deepseek", + temperature=0, + api_key=os.getenv("DEEPSEEK-API-KEY") + ) + ) + llm.provider.mcp.add_server(name=codeTideMCPServer.name, parameters=codeTideMCPServer) + return llm +``` + +This setup allows the LLM to leverage CodeTide’s tools for codebase interactions. + +CodeTide can now be used as an MCP (Multi-Code Processor) Server! This allows seamless integration with AI tools and workflows. Below are the tools available: + +- **applyPatch**: Apply structured patches to the filesystem. +- **getContext**: Retrieve code context for identifiers. +- **getRepoTree**: Generate a visual tree representation of the repository. +- **checkCodeIdentifiers**: Validate code identifiers and suggest corrections. + ## ⚙️ Installation ### 📦 From PyPI From 4cdf75f92b82f0269a395bb1cfa67454d4f9ab2f Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Wed, 2 Jul 2025 22:59:25 +0100 Subject: [PATCH 28/88] Update resolve_intra_file_dependencies method to accept Optional list of CodeFileModel --- codetide/parsers/base_parser.py | 2 +- codetide/parsers/generic_parser.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codetide/parsers/base_parser.py b/codetide/parsers/base_parser.py index 54ce61a..562590f 100644 --- a/codetide/parsers/base_parser.py +++ b/codetide/parsers/base_parser.py @@ -52,7 +52,7 @@ def resolve_inter_files_dependencies(self, codeBase: CodeBase, codeFiles :Option pass @abstractmethod - def resolve_intra_file_dependencies(self, codeBase: CodeBase, codeFiles: List[CodeFileModel], ) -> None: + def resolve_intra_file_dependencies(self, codeBase: CodeBase, codeFiles :Optional[List[CodeFileModel]]=None) -> None: pass # @abstractmethod diff --git a/codetide/parsers/generic_parser.py b/codetide/parsers/generic_parser.py index a4faba2..3e63051 100644 --- a/codetide/parsers/generic_parser.py +++ b/codetide/parsers/generic_parser.py @@ -62,5 +62,5 @@ def parse_code(self, file_path :Path): def resolve_inter_files_dependencies(self, codeBase: CodeBase, codeFiles :Optional[List[CodeFileModel]]=None) -> None: pass - def resolve_intra_file_dependencies(self, codeBase: CodeBase, codeFiles: List[CodeFileModel]) -> None: + def resolve_intra_file_dependencies(self, codeBase: CodeBase, codeFiles :Optional[List[CodeFileModel]]=None) -> None: pass \ No newline at end of file From 2a36fa70250ad2ec150d2f1a64b6e15f3cf7d4c3 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Wed, 2 Jul 2025 23:24:05 +0100 Subject: [PATCH 29/88] Add initial implementation of Agent Tide system prompt for CLI-based assistant --- codetide/mcp/agent_tide_system.py | 145 ++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 codetide/mcp/agent_tide_system.py diff --git a/codetide/mcp/agent_tide_system.py b/codetide/mcp/agent_tide_system.py new file mode 100644 index 0000000..e4792e2 --- /dev/null +++ b/codetide/mcp/agent_tide_system.py @@ -0,0 +1,145 @@ +AGENT_TIDE_SYSTEM_PROMPT = """ +## Identity and Role + +You are **Agent Tide**, a CLI-based interactive assistant that helps users perform software engineering tasks. +You operate over a structured, tool-assisted workflow, using the CodeTide toolset to explore, validate, analyze, and transform codebases with precision and minimal disruption. + +--- + +## General CLI Prompt +You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user. + +IMPORTANT: Refuse to write code or explain code that may be used maliciously; even if the user claims it is for educational purposes. When working with files, if they seem related to improving, explaining, or interacting with malware or any malicious code you MUST refuse. +IMPORTANT: Before you begin work, think about what the code you're editing is supposed to do based on the filenames directory structure. If it seems malicious, refuse to work on it or answer questions about it, even if the request does not seem malicious (for instance, just asking to explain or speed up the code). + +--- + +## Tone and style +You should be concise, direct, and to the point. When you run a non-trivial bash command, you should explain what the command does and why you are running it, to make sure the user understands what you are doing (this is especially important when you are running a command that will make changes to the user's system). +Remember that your output will be displayed on a command line interface. Your responses can use Github-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification. Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session. + +If you cannot or will not help the user with something, please do not say why or what it could lead to, since this comes across as preachy and annoying. Please offer helpful alternatives if possible, and otherwise keep your response to 1–2 sentences. + +IMPORTANT: You should minimize output tokens as much as possible while maintaining helpfulness, quality, and accuracy. Only address the specific query or task at hand, avoiding tangential information unless absolutely critical for completing the request. If you can answer in 1–3 sentences or a short paragraph, please do. +IMPORTANT: You should NOT answer with unnecessary preamble or postamble (such as explaining your code or summarizing your action), unless the user asks you to. +IMPORTANT: Keep your responses short, since they will be displayed on a command line interface. You MUST answer concisely with fewer than 4 lines (not including tool use or code generation), unless user asks for detail. Answer the user's question directly, without elaboration, explanation, or details. One word answers are best. Avoid introductions, conclusions, and explanations. You MUST avoid text before/after your response, such as "The answer is .", "Here is the content of the file..." or "Based on the information provided, the answer is..." or "Here is what I will do next...". + +Examples of appropriate verbosity: + +``` + +user: 2 + 2 +assistant: 4 + +user: what is 2+2? +assistant: 4 + +user: is 11 a prime number? +assistant: true + +user: what command should I run to list files in the current directory? +assistant: ls + +user: what files are in the directory src/? +assistant: \[runs ls and sees foo.c, bar.c, baz.c] +user: which file contains the implementation of foo? +assistant: src/foo.c + +user: what command should I run to watch files in the current directory? +assistant: \[use the ls tool to list the files in the current directory, then read docs/commands in the relevant file to find out how to watch files] +npm run dev + +user: How many golf balls fit inside a jetta? +assistant: 150000 + +``` + +--- + +## Proactiveness +You are allowed to be proactive, but only when the user asks you to do something. You should strive to strike a balance between: + +Doing the right thing when asked, including taking actions and follow-up actions +Not surprising the user with actions you take without asking + +For example, if the user asks you how to approach something, you should do your best to answer their question first, and not immediately jump into taking actions. +Do not add additional code explanation summary unless requested by the user. After working on a file, just stop, rather than providing an explanation of what you did. + +## Following conventions +When making changes to files, first understand the file's code conventions. Mimic code style, use existing libraries and utilities, and follow existing patterns. + +NEVER assume that a given library is available, even if it is well known. Whenever you write code that uses a library or framework, first check that this codebase already uses the given library. For example, you might look at neighboring files, or check the package.json (or cargo.toml, and so on depending on the language). +When you create a new component, first look at existing components to see how they're written; then consider framework choice, naming conventions, typing, and other conventions. +When you edit a piece of code, first look at the code's surrounding context (especially its imports) to understand the code's choice of frameworks and libraries. Then consider how to make the given change in a way that is most idiomatic. +Always follow security best practices. Never introduce code that exposes or logs secrets and keys. Never commit secrets or keys to the repository. + +## Code style +Do not add comments to the code you write, unless the user asks you to, or the code is complex and requires additional context. + +## Doing tasks +The user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended: + +Use the available search tools to understand the codebase and the user's query. You are encouraged to use the search tools extensively both in parallel and sequentially. +Implement the solution using all tools available to you +Verify the solution if possible with tests. NEVER assume specific test framework or test script. Check the README or search codebase to determine the testing approach. + +--- + +## Available Tools + +You must use the following tools to perform your tasks: + +### `getRepoTree(show_contents: bool, show_types: bool = False) → str` +Returns an ASCII tree of the repo. Use `show_contents=True` to include top-level class/function/method names. + +### `checkCodeIdentifiers(code_identifiers: List[str]) → str` +Checks whether each identifier (in dot or slash notation) is valid. Returns suggestions if any are incorrect. + +### `getContext(code_identifiers: List[str], context_depth: int = 1) → str` +Loads declarations, imports, and references for the given identifiers. Use batching for cross-referencing. `context_depth` controls how deep to follow references. + +### `applyPatch(patch_text: str) → str` +Applies one or more patch blocks to the codebase. Patches must follow strict syntax and are applied atomically. Errors (e.g., `FileNotFoundError`, `DiffError`) will fail the entire patch. + +--- + +## Core Workflow + +1. **Explore the Codebase** + Use `getRepoTree(show_contents=True)` to discover files, classes, and functions. + +2. **Validate Targets** + Use `checkCodeIdentifiers([...])` to ensure identifiers are correct before loading context or making changes. + +3. **Load Context** + Use `getContext([...], context_depth)` to retrieve relevant code and dependencies. Use batching for related items. + +4. **Modify Code** + Use `applyPatch(...)` to apply formatted patches with Add/Update/Delete/Move operations. + +--- + +## Execution Principles + +See **Proactiveness**, **Following conventions**, **Code style**, and **Doing tasks** above. + +--- + +## Output Style + +- Use **concise, monospace-formatted Markdown** output. +- Avoid unnecessary verbosity. +- Never wrap answers in phrases like *"Here is..."*, *"Based on..."*, etc. +- One-word or one-line responses are often best. +- Do not explain bash or code unless explicitly asked. + +--- + +## CLI Behavior + +- You are a command-line interface tool. +- Output will be rendered in monospace Markdown. +- Communicate only through output text or tool use. +- Never simulate bash commands or add communication via code comments. + +""" \ No newline at end of file From 47a4ea53f5a826fd8f71c1101d6318254e325173 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Wed, 2 Jul 2025 23:34:25 +0100 Subject: [PATCH 30/88] Replace MCP_USE_INSTRUCTIONS with AGENT_TIDE_SYSTEM_PROMPT in FastMCP initialization --- codetide/mcp/server.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/codetide/mcp/server.py b/codetide/mcp/server.py index 58c303a..f8da2d1 100644 --- a/codetide/mcp/server.py +++ b/codetide/mcp/server.py @@ -1,8 +1,7 @@ +from .agent_tide_system import AGENT_TIDE_SYSTEM_PROMPT from fastmcp import FastMCP -MCP_USE_INSTRUCTIONS = """""" - codeTideMCPServer = FastMCP( name="CodeTide", - # instructions=MCP_USE_INSTRUCTIONS, + instructions=AGENT_TIDE_SYSTEM_PROMPT, ) From f53b2ea8266a3df4e4ab9109369d2715a6f73e21 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 4 Jul 2025 00:52:46 +0100 Subject: [PATCH 31/88] Enhance agent_tide.py to include keyboard input handling for graceful exit and remove redundant AGENT_TIDE_SYSTEM_PROMPT definition --- examples/agent_tide.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/examples/agent_tide.py b/examples/agent_tide.py index a74fd47..d5b08de 100644 --- a/examples/agent_tide.py +++ b/examples/agent_tide.py @@ -3,10 +3,12 @@ Make sure to set `CODETIDE_WORKSPACE` env var which will be requried for the MCP server to work """ +from codetide.mcp.agent_tide_system import AGENT_TIDE_SYSTEM_PROMPT +from codetide.mcp import codeTideMCPServer from aicore.llm import Llm, LlmConfig from aicore.logger import _logger -from codetide.mcp import codeTideMCPServer from typing import Optional +import keyboard import os AGENT_TIDE_ASCII_ART = """ @@ -38,10 +40,6 @@ def trim_messages(messages, tokenizer_fn, max_tokens :Optional[int]=None): while messages and sum(len(tokenizer_fn(msg)) for msg in messages) > max_tokens: messages.pop(0) # Remove from the beginning -AGENT_TIDE_SYSTEM_PROMPT = """ -You are a software engineer with tool calling capabilities who will help the user navigate his codebase -""" - async def main(max_tokens :int=48000): llm = init_llm() history = [] @@ -53,8 +51,14 @@ async def main(max_tokens :int=48000): message = input("You: ").strip() if not message: continue + + if keyboard.is_pressed('esc'): + _logger.warning("Escape key pressed — exiting...") + break + except EOFError: - break # Ctrl+D on Unix, Ctrl+Z on Windows + break + except KeyboardInterrupt: _logger.logger.warning("\nExiting...") break From ceea48f133b280931a8fe08176eb5d89d0136730 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 4 Jul 2025 19:40:12 +0100 Subject: [PATCH 32/88] Refactor find_context and peek_next_section for improved handling of context lines and added buffering for trailing context --- codetide/mcp/tools/patch_code/parser.py | 45 +++++++++++++++++++------ 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/codetide/mcp/tools/patch_code/parser.py b/codetide/mcp/tools/patch_code/parser.py index d43f276..2e611bb 100644 --- a/codetide/mcp/tools/patch_code/parser.py +++ b/codetide/mcp/tools/patch_code/parser.py @@ -37,13 +37,13 @@ def find_context( lines: List[str], context: List[str], start: int, eof: bool ) -> Tuple[int, int]: """Finds the location of a context block, allowing for fuzziness.""" - if eof and context: - end_start_idx = len(lines) - len(context) - if end_start_idx >= 0: - new_index, fuzz = find_context_core(lines, context, end_start_idx) - if new_index == end_start_idx: - return new_index, fuzz - return -1, 0 + # if eof and context: + # end_start_idx = len(lines) - len(context) + # if end_start_idx >= 0: + # new_index, fuzz = find_context_core(lines, context, end_start_idx) + # if new_index == end_start_idx: + # return new_index, fuzz + # return -1, 0 return find_context_core(lines, context, start) @@ -59,6 +59,8 @@ def peek_next_section( ins_lines: List[str] = [] chunks: List[Chunk] = [] mode = "keep" + pending_keep_lines: List[str] = [] # Buffer for keep lines that might be at the end + has_had_changes = False # Track if we've seen any add/delete operations while index < len(lines): norm_s = Parser._norm(lines[index]) @@ -103,14 +105,37 @@ def peek_next_section( del_lines, ins_lines = [], [] if mode == "delete": + has_had_changes = True + # Add any pending keep lines before processing delete lines + if pending_keep_lines: + context_lines.extend(pending_keep_lines) + pending_keep_lines = [] + del_lines.append(content) context_lines.append(content) elif mode == "add": + has_had_changes = True + # Add any pending keep lines before processing add lines + if pending_keep_lines: + context_lines.extend(pending_keep_lines) + pending_keep_lines = [] + ins_lines.append(content) elif mode == "keep": - context_lines.append(content) + if has_had_changes: + # We've seen changes, so these keep lines could be trailing context + # Buffer them in case they're the final block + pending_keep_lines.append(content) + else: + # No changes seen yet, so these are leading context - add them directly + context_lines.append(content) if ins_lines or del_lines: + # There are pending changes, so add any pending keep lines as they're part of the context + if pending_keep_lines: + context_lines.extend(pending_keep_lines) + pending_keep_lines = [] + chunks.append( Chunk( orig_index=len(context_lines) - len(del_lines), @@ -123,10 +148,8 @@ def peek_next_section( if index < len(lines) and Parser._norm(lines[index]) == "*** End of File": index += 1 is_eof = True - return context_lines, chunks, index, is_eof - # --------------------------------------------------------------------------- # # Patch → Commit and Commit application # --------------------------------------------------------------------------- # @@ -310,7 +333,7 @@ def _parse_update_file(self, text: str, path_for_error: str) -> PatchAction: ): break continue - + print(f"{orig_lines=}") new_index, fuzz = find_context(orig_lines, next_ctx, search_start_idx, eof) if new_index == -1: ctx_txt = "\n".join(next_ctx) From 659f43a97ea07841523120983d55b0062a1d46b2 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 4 Jul 2025 19:48:01 +0100 Subject: [PATCH 33/88] Add tests for peek_next_section functionality in apply_patch.py --- tests/mcp/tools/test_apply_patch.py | 117 ++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/tests/mcp/tools/test_apply_patch.py b/tests/mcp/tools/test_apply_patch.py index 54a2b43..e420a67 100644 --- a/tests/mcp/tools/test_apply_patch.py +++ b/tests/mcp/tools/test_apply_patch.py @@ -7,6 +7,8 @@ import pytest import re +from codetide.mcp.tools.patch_code.parser import peek_next_section + class MockFileSystem: """Mock filesystem for testing.""" @@ -146,6 +148,8 @@ def test_update_end_of_file_marker(mock_fs): def test_rename_file(mock_fs): + # The original file has "line1\nline2\nline3\n" + # We need to match this content exactly patch = """*** Begin Patch *** Update File: data/old_data.txt *** Move to: data/new_data.txt @@ -178,6 +182,119 @@ def test_crlf_handling(mock_fs): mock_fs.apply_patch(patch) assert mock_fs.fs['crlf.txt'] == "line one\nline 2\nline three\n" +def test_peek_next_section_basic(): + """Test the basic functionality of peek_next_section""" + lines = [ + " line1", # keep (note: single space prefix) + "-line2", # delete + "+line_two", # add + " line3", # keep + "*** End of File" + ] + + context_lines, chunks, index, is_eof = peek_next_section(lines, 0) + + # context_lines should contain the original content (keep + delete lines) + # The content is the part after the prefix character + assert context_lines == ["line1", "line2"] + + # Should have one chunk with the change + assert len(chunks) == 1 + chunk = chunks[0] + assert chunk.del_lines == ["line2"] + assert chunk.ins_lines == ["line_two"] + assert chunk.orig_index == 1 # Position where the change occurs + + assert is_eof is True + assert index == 5 # Should point past the "*** End of File" line + + +def test_peek_next_section_multiple_changes(): + """Test peek_next_section with multiple change blocks""" + lines = [ + " line1", # keep + "-line2", # delete + "+line_two", # add + " line3", # keep + "-line4", # delete + "+line_four", # add + " line5", # keep + "*** End of File" + ] + + context_lines, chunks, index, is_eof = peek_next_section(lines, 0) + + # context_lines should contain original content (keep + delete lines) + assert context_lines == ["line1", "line2", "line3", "line4"] + + # Should have two chunks + assert len(chunks) == 2 + + # First chunk + assert chunks[0].del_lines == ["line2"] + assert chunks[0].ins_lines == ["line_two"] + assert chunks[0].orig_index == 1 + + # Second chunk + assert chunks[1].del_lines == ["line4"] + assert chunks[1].ins_lines == ["line_four"] + assert chunks[1].orig_index == 3 # Position in original content + + +def test_peek_next_section_trailing_keep_lines(): + """Test that trailing keep lines are handled correctly""" + lines = [ + "-line1", # delete + "+line_one", # add + " line2", # keep + " line3", # keep - these are trailing + " line4", # keep - these are trailing + "*** End of File" + ] + + context_lines, chunks, index, is_eof = peek_next_section(lines, 0) + + # All lines should be in context_lines (original content) + assert context_lines == ["line1"] + + # Should have one chunk + assert len(chunks) == 1 + assert chunks[0].del_lines == ["line1"] + assert chunks[0].ins_lines == ["line_one"] + assert chunks[0].orig_index == 0 + + +def test_peek_next_section_only_keep_lines(): + """Test what happens with only keep lines (no changes)""" + lines = [ + " line1", # keep + " line2", # keep + " line3", # keep + "*** End of File" + ] + + context_lines, chunks, index, is_eof = peek_next_section(lines, 0) + + # All lines should be in context_lines + assert context_lines == ["line1", "line2", "line3"] + + # Should have no chunks since there are no changes + assert len(chunks) == 0 + + assert is_eof is True + + +def test_peek_next_section_empty(): + """Test with empty input""" + lines = [ + "*** End of File" + ] + + context_lines, chunks, index, is_eof = peek_next_section(lines, 0) + + assert context_lines == [] + assert len(chunks) == 0 + assert is_eof is True def test_fuzzy_matching_whitespace(mock_fs): # The patch context has different surrounding whitespace than the original file From fcaba2ba50876cfd82ea247aca9286c38065a628 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 4 Jul 2025 20:11:21 +0100 Subject: [PATCH 34/88] Enhance applyPatch function documentation with detailed patch format guidelines and critical rules for updates; ensure temporary patch files are removed on success. --- codetide/mcp/tools/apply_patch.py | 182 +++++++++++++++++++++--------- 1 file changed, 127 insertions(+), 55 deletions(-) diff --git a/codetide/mcp/tools/apply_patch.py b/codetide/mcp/tools/apply_patch.py index 55d4db1..778b826 100644 --- a/codetide/mcp/tools/apply_patch.py +++ b/codetide/mcp/tools/apply_patch.py @@ -1,75 +1,144 @@ from .patch_code import DiffError, file_exists, open_file, process_patch, remove_file, write_file from ..server import codeTideMCPServer +from ...core.common import writeFile from ..utils import initCodeTide +from ulid import ulid +import os + @codeTideMCPServer.tool async def applyPatch(patch_text: str) -> str: """ - Apply structured patches to the filesystem. - - Patch Format: - - Wrap in: *** Begin Patch ... *** End Patch - - Supports: Add, Update, Delete, Move (via Update + Move to) - - Syntax: - - Context lines: ' ' - - Removed lines: '-' - - Added lines: '+' - - Change blocks: start with '@@' - - Use '*** End of File' to update file endings - - Relative paths only - - Key Features: - - Fuzzy matching, CRLF normalization - - Multiple hunks per file - - Atomic: all or nothing - - Validates file existence (no overwrites on Add) - - Errors: - - FileNotFoundError: missing file for Update/Delete - - DiffError: bad format, context mismatch, or conflict - - DiffError: Add/Move to already-existing file - - LLM Tips: - 1. Include 2-3 context lines around changes - 2. Preserve indentation/whitespace - 3. Prefer small, isolated patches - 4. Add full content for new files (with imports/defs) - 5. Ensure move destinations exist or are creatable - - Examples: - # Add file - *** Begin Patch - *** Add File: new.py - +print("Hello") - *** End Patch + Call this tool with a diff-like text to make file changes. Whenever you need to write code, this is the tool for you. + It applies structured patches to files in a workspace for **adding, deleting, moving, or modifying** code in a reliable, verifiable, and line-accurate way. - # Update file - *** Begin Patch - *** Update File: main.py - @@ def greet(): - -print("Hi") - +print("Hello") - *** End Patch + --- + + ## CRITICAL RULES FOR `*** Update File` + + You are generating a unified diff — **not a full file replacement**. + + ### YOU MUST: + - Use only the **original file content** to construct the patch (` ` context lines, `-` deletions, `+` additions). + - Match all context and deleted lines **exactly**, including: + - leading modifiers (e.g., `async`, `@staticmethod`, etc.) + - indentation and whitespace + - line endings + - Include **only what changed**, and minimal surrounding context to anchor the diff. + - Use **complete `@@` hunk headers**, exactly matching the **entire first line** of the block being changed. + - If the block is a function or method, the `@@` header must include all modifiers (e.g., `async def`, `@classmethod def`, etc.) and match the original line exactly. + + ### YOU MUST NOT: + - Truncate or invent function headers in `@@` lines. + - Include extra unchanged code beyond the diff area. + - Guess or partially reconstruct lines — **copy the original exactly**. + - Leave `@@` headers incomplete (this will cause the patch to fail). + - Include unrelated changes in the same patch block. - # Delete file + --- + + ## GOOD PATCH EXAMPLE + + ```diff *** Begin Patch - *** Delete File: old.py + *** Update File: app/utils/io.py + @@ async def fetch_data(session, url): + async def fetch_data(session, url): + - async with session.get(url) as resp: + + async with session.get(url, timeout=10) as resp: *** End Patch + ```` + + This is correct because: + + * The `@@` header includes the full original line, including the `async` modifier. + * Only the changed line is included. + * No extra or unrelated code is added. + + --- + + ## BAD PATCH EXAMPLES + + ### Missing modifier in `@@` header + + ```diff + @@ def fetch_data(session, url): + ``` + + Incorrect — the original line starts with `async def`. The `@@` header must match exactly: - # Move with update + ```diff + @@ async def fetch_data(session, url): + ``` + + --- + + ### Truncated `@@` header + + ```diff + @@ def process_node(cls, node): + ``` + + Incorrect — this is only part of the signature. You must include the full original line: + + ```diff + @@ def process_node(cls, node: Node, code: bytes, file: CodeFileModel, flag: bool=False): + ``` + + --- + + ### Extra unchanged lines + + ```diff + @@ def run(): + def run(): + - result = old() + + result = new() + return result + ``` + + Incorrect — `return result` is unchanged and should not appear. Correct: + + ```diff + @@ def run(): + def run(): + - result = old() + + result = new() + ``` + + --- + + ## PATCH FORMAT SUMMARY + + ```diff *** Begin Patch - *** Update File: a.py - *** Move to: b.py - @@ def f(): - -print("x") - +print("y") - *** End of File + *** Update File: path/to/file.py + @@ full, original line from code block + context line (unchanged, prefix: space) + - line being deleted + + line being added *** End Patch + ``` + + * The `@@` line must match the **entire first line of the block**, including any modifiers like `async`. + * Do not shorten, guess, or reformat the line — copy it exactly from the original. + + --- + + ## FINAL REMINDERS + + * Only generate diffs against the file's original content. + * Avoid including unrelated or unchanged code. + * Match indentation, whitespace, and modifiers exactly. + * Keep each patch as small, accurate, and focused as possible. + * One logical change per patch. """ + patch_path = f"./storage/{ulid()}.txt" try: - print(f"{patch_text=}") + patch_text = patch_text.replace("\'", "'") + patch_text = patch_text.replace('\"', '"') + writeFile(patch_text, patch_path) result = process_patch(patch_text, open_file, write_file, remove_file, file_exists) _ = await initCodeTide() @@ -85,4 +154,7 @@ async def applyPatch(patch_text: str) -> str: result = f"An unexpected error occurred: {exc}" raise exc + if "exc" not in locals(): + os.remove(patch_path) + return result \ No newline at end of file From 3b670f4f9c23c00ceaaa1f0c435be07898a0bcfc Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 4 Jul 2025 20:21:45 +0100 Subject: [PATCH 35/88] Refactor applyPatch function to improve patch handling; remove unnecessary os import and ensure patch is written before processing. --- codetide/mcp/tools/apply_patch.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/codetide/mcp/tools/apply_patch.py b/codetide/mcp/tools/apply_patch.py index 778b826..5f0337a 100644 --- a/codetide/mcp/tools/apply_patch.py +++ b/codetide/mcp/tools/apply_patch.py @@ -4,7 +4,6 @@ from ..utils import initCodeTide from ulid import ulid -import os @codeTideMCPServer.tool async def applyPatch(patch_text: str) -> str: @@ -138,7 +137,6 @@ def run(): try: patch_text = patch_text.replace("\'", "'") patch_text = patch_text.replace('\"', '"') - writeFile(patch_text, patch_path) result = process_patch(patch_text, open_file, write_file, remove_file, file_exists) _ = await initCodeTide() @@ -155,6 +153,17 @@ def run(): raise exc if "exc" not in locals(): - os.remove(patch_path) + writeFile(patch_text, patch_path) + + return result + +if __name__ == "__main__": + from codetide.core.common import writeFile, readFile + from codetide.mcp.tools.patch_code import DiffError, file_exists, open_file, process_patch, remove_file, write_file + from codetide.mcp.server import codeTideMCPServer + from codetide.mcp.utils import initCodeTide - return result \ No newline at end of file + import asyncio + patch_path = "./storage/01JZ9969DN4A8C98HH2CXVXAFF.txt" + patch = readFile(patch_path) + asyncio.run(applyPatch(patch)) \ No newline at end of file From 42a88f3e43b9718e4ad0c77fdbebeccfdd53531f Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 4 Jul 2025 20:36:20 +0100 Subject: [PATCH 36/88] feat: updated agent tide prompt --- codetide/mcp/tools/apply_patch.py | 96 +++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 30 deletions(-) diff --git a/codetide/mcp/tools/apply_patch.py b/codetide/mcp/tools/apply_patch.py index 5f0337a..4e08b5b 100644 --- a/codetide/mcp/tools/apply_patch.py +++ b/codetide/mcp/tools/apply_patch.py @@ -20,23 +20,25 @@ async def applyPatch(patch_text: str) -> str: ### YOU MUST: - Use only the **original file content** to construct the patch (` ` context lines, `-` deletions, `+` additions). - Match all context and deleted lines **exactly**, including: - - leading modifiers (e.g., `async`, `@staticmethod`, etc.) + - modifiers (e.g., `async`, `@staticmethod`, `@classmethod`, etc.) - indentation and whitespace - line endings - - Include **only what changed**, and minimal surrounding context to anchor the diff. - - Use **complete `@@` hunk headers**, exactly matching the **entire first line** of the block being changed. - - If the block is a function or method, the `@@` header must include all modifiers (e.g., `async def`, `@classmethod def`, etc.) and match the original line exactly. + - Include only the minimal context needed around your change. + - Use **complete and exact `@@` hunk headers** that match the **first line** of the block being edited — no truncation, no guessing, no partial lines. + - The `@@` line must be a **verbatim copy** of the first line of the affected block. + - If the function is `async def`, the `@@` must start with `@@ async def ...`. + - If a classmethod or staticmethod, copy the exact function line, modifiers and all. ### YOU MUST NOT: - - Truncate or invent function headers in `@@` lines. - - Include extra unchanged code beyond the diff area. - - Guess or partially reconstruct lines — **copy the original exactly**. - - Leave `@@` headers incomplete (this will cause the patch to fail). - - Include unrelated changes in the same patch block. + - Include numeric line indicators inside the `@@` header (e.g., `@@ -42,7 +42,8 @@`). This is **not supported**. Only the code line is allowed. + - Truncate or invent function headers in the `@@` line. + - Include extra unchanged lines after or before the patch hunk. + - Include unrelated code or changes in the same patch. + - Add unchanged lines beyond the immediate context. --- - ## GOOD PATCH EXAMPLE + ## GOOD PATCH EXAMPLE (SINGLE-LINE CHANGE) ```diff *** Begin Patch @@ -48,23 +50,40 @@ async def fetch_data(session, url): *** End Patch ```` - This is correct because: + --- - * The `@@` header includes the full original line, including the `async` modifier. - * Only the changed line is included. - * No extra or unrelated code is added. + ## GOOD PATCH EXAMPLE (MULTI-LINE CHANGE) + + ```diff + *** Begin Patch + *** Update File: services/engine.py + @@ def process_data(data: list[str]) -> dict[str, int]: + def process_data(data: list[str]) -> dict[str, int]: + - counts = {} + - for item in data: + - if item in counts: + - counts[item] += 1 + - else: + - counts[item] = 1 + - return counts + + from collections import Counter + + return dict(Counter(data)) + *** End Patch + ``` + + This is valid and encouraged when simplifying or replacing logic. --- ## BAD PATCH EXAMPLES - ### Missing modifier in `@@` header + ### 1. Missing modifier in `@@` header ```diff @@ def fetch_data(session, url): ``` - Incorrect — the original line starts with `async def`. The `@@` header must match exactly: + Incorrect — the original function is `async`, so this must be: ```diff @@ async def fetch_data(session, url): @@ -72,13 +91,13 @@ async def fetch_data(session, url): --- - ### Truncated `@@` header + ### 2. Truncated function signature ```diff @@ def process_node(cls, node): ``` - Incorrect — this is only part of the signature. You must include the full original line: + Incorrect — the full original signature must be used: ```diff @@ def process_node(cls, node: Node, code: bytes, file: CodeFileModel, flag: bool=False): @@ -86,7 +105,21 @@ async def fetch_data(session, url): --- - ### Extra unchanged lines + ### 3. `@@` header with line numbers (not supported) + + ```diff + @@ -42,7 +42,8 @@ + ``` + + Invalid — we do **not** support numeric line-based hunk headers. Use a code-based header that matches the first line of the block, like: + + ```diff + @@ def handle_response(response: Response) -> Result: + ``` + + --- + + ### 4. Including unchanged lines after the patch ```diff @@ def run(): @@ -96,7 +129,7 @@ def run(): return result ``` - Incorrect — `return result` is unchanged and should not appear. Correct: + Incorrect — `return result` is unchanged and should be excluded: ```diff @@ def run(): @@ -112,25 +145,28 @@ def run(): ```diff *** Begin Patch *** Update File: path/to/file.py - @@ full, original line from code block + @@ exact first line from original block context line (unchanged, prefix: space) - - line being deleted - + line being added + - line being deleted (must match exactly) + + line being added (your change) *** End Patch ``` - * The `@@` line must match the **entire first line of the block**, including any modifiers like `async`. - * Do not shorten, guess, or reformat the line — copy it exactly from the original. + ### The `@@` header must: + + * Contain **only the original line of code** (no line numbers) + * Be an **exact, one-to-one match** + * Include any relevant modifiers (e.g., `async`, decorators, etc.) --- ## FINAL REMINDERS - * Only generate diffs against the file's original content. - * Avoid including unrelated or unchanged code. - * Match indentation, whitespace, and modifiers exactly. - * Keep each patch as small, accurate, and focused as possible. - * One logical change per patch. + * Generate patches against the original file only. + * Do not include full file contents — only the relevant diffs. + * Avoid unrelated edits — one logical change per hunk. + * Use clean, minimal, verifiable diffs. + * Always match formatting, indentation, and block structure precisely. """ patch_path = f"./storage/{ulid()}.txt" From 6a0ce05ac605bb8674e637bd7235ba521a99925e Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 4 Jul 2025 20:51:41 +0100 Subject: [PATCH 37/88] feat: implement async prompt handling and key bindings for exit functionality in agent_tide --- examples/agent_tide.py | 48 ++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/examples/agent_tide.py b/examples/agent_tide.py index d5b08de..d5c82e8 100644 --- a/examples/agent_tide.py +++ b/examples/agent_tide.py @@ -7,8 +7,10 @@ from codetide.mcp import codeTideMCPServer from aicore.llm import Llm, LlmConfig from aicore.logger import _logger + +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit import PromptSession from typing import Optional -import keyboard import os AGENT_TIDE_ASCII_ART = """ @@ -40,37 +42,51 @@ def trim_messages(messages, tokenizer_fn, max_tokens :Optional[int]=None): while messages and sum(len(tokenizer_fn(msg)) for msg in messages) > max_tokens: messages.pop(0) # Remove from the beginning -async def main(max_tokens :int=48000): +async def main(max_tokens: int = 48000): llm = init_llm() history = [] - _logger.logger.info(F"\n{AGENT_TIDE_ASCII_ART}\nReady to surf. Press ESC to exit.\n") + # 1. Set up key bindings + bindings = KeyBindings() + + @bindings.add('escape') + def _(event): + """When Esc is pressed, exit the application.""" + _logger.logger.warning("Escape key pressed — exiting...") + event.app.exit() + + # 2. Create a prompt session with the custom key bindings + session = PromptSession(key_bindings=bindings) + + _logger.logger.info(f"\n{AGENT_TIDE_ASCII_ART}\nReady to surf. Press ESC to exit.\n") try: while True: try: - message = input("You: ").strip() + # 3. Use the async prompt instead of input() + message = await session.prompt_async("You: ") + message = message.strip() + if not message: continue - if keyboard.is_pressed('esc'): - _logger.warning("Escape key pressed — exiting...") - break - - except EOFError: - break - - except KeyboardInterrupt: - _logger.logger.warning("\nExiting...") + except (EOFError, KeyboardInterrupt): + # prompt_toolkit raises EOFError on Ctrl-D and KeyboardInterrupt on Ctrl-C + _logger.warning("\nExiting...") break history.append(message) trim_messages(history, llm.tokenizer, max_tokens) + print("Agent: Thinking...") response = await llm.acomplete(history, system_prompt=[AGENT_TIDE_SYSTEM_PROMPT]) + print(f"Agent: {response}") history.append(response) - - except KeyboardInterrupt: - _logger.logger.warning("\nExited by user.") + + except asyncio.CancelledError: + # This can happen if the event loop is shut down + pass + finally: + _logger.logger.info("\nExited by user. Goodbye!") if __name__ == "__main__": from dotenv import load_dotenv From c3aa4193b4458927f442cd5fd72828ebc2de0535 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 4 Jul 2025 23:19:27 +0100 Subject: [PATCH 38/88] fix: correct formatting of context_depth descriptions in getContext function --- codetide/mcp/tools/get_context.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codetide/mcp/tools/get_context.py b/codetide/mcp/tools/get_context.py index c483178..cc5f1fd 100644 --- a/codetide/mcp/tools/get_context.py +++ b/codetide/mcp/tools/get_context.py @@ -19,9 +19,9 @@ async def getContext( cross-referencing and performance. context_depth (int): Reference depth: - 0 – Only requested elements - 1 – + Direct references (default) - 2+ – + Recursive references + 0 : Only requested elements + 1 : + Direct references (default) + 2+: + Recursive references Returns: str: Formatted context including: From 0dcbab9abaa16698aded1ec9198b220bde635b4f Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 5 Jul 2025 00:01:39 +0100 Subject: [PATCH 39/88] refactor: rename getContext to getCodeContext and update imports; remove deprecated get_context.py --- codetide/mcp/__init__.py | 4 +-- codetide/mcp/tools/__init__.py | 4 +-- codetide/mcp/tools/get_code_context.py | 49 ++++++++++++++++++++++++++ codetide/mcp/tools/get_context.py | 49 -------------------------- examples/agent_tide.py | 4 +-- 5 files changed, 55 insertions(+), 55 deletions(-) create mode 100644 codetide/mcp/tools/get_code_context.py delete mode 100644 codetide/mcp/tools/get_context.py diff --git a/codetide/mcp/__init__.py b/codetide/mcp/__init__.py index 9d90653..85e7fef 100644 --- a/codetide/mcp/__init__.py +++ b/codetide/mcp/__init__.py @@ -1,9 +1,9 @@ from .server import codeTideMCPServer -from .tools import getContext, getRepoTree, checkCodeIdentifiers, applyPatch +from .tools import getCodeContext, getRepoTree, checkCodeIdentifiers, applyPatch __all__ = [ "codeTideMCPServer", - "getContext", + "getCodeContext", "getRepoTree", "checkCodeIdentifiers", "applyPatch" diff --git a/codetide/mcp/tools/__init__.py b/codetide/mcp/tools/__init__.py index 3b10415..741f419 100644 --- a/codetide/mcp/tools/__init__.py +++ b/codetide/mcp/tools/__init__.py @@ -1,10 +1,10 @@ -from .get_context import getContext +from .get_code_context import getCodeContext from .get_repo_tree import getRepoTree from .check_code_identifiers import checkCodeIdentifiers from .apply_patch import applyPatch __all__ = [ - "getContext", + "getCodeContext", "getRepoTree", "checkCodeIdentifiers", "applyPatch" diff --git a/codetide/mcp/tools/get_code_context.py b/codetide/mcp/tools/get_code_context.py new file mode 100644 index 0000000..c9d6366 --- /dev/null +++ b/codetide/mcp/tools/get_code_context.py @@ -0,0 +1,49 @@ +from ..server import codeTideMCPServer +from ..utils import initCodeTide + +from typing import List + +@codeTideMCPServer.tool +async def getCodeContext( + code_identifiers: List[str], + context_depth: int = 1) -> str: + """ + Retrieve all required code context in a single call. + + This function must be called only once per user task. To ensure efficient and consistent reasoning: + - Always batch all necessary `code_identifiers` into a single `getContext(...)` call. + - Never issue multiple `getContext` calls for the same request unless new, unresolved symbols are introduced. + + Arguments: + code_identifiers (List[str]): + A list of dot- or slash-form identifiers to retrieve, e.g.: + - 'package.module.Class.method' + - 'tests/module/TestClass/test_method' + - 'src/module.py' + Use full paths and correct casing. Prefer batching for performance and cross-reference resolution. + + context_depth (int): + Controls how deeply to follow references: + - 0: Only the specified symbol(s) + - 1: + Direct references (default) + - 2+: + Recursive references + + Returns: + str: Formatted, syntax-highlighted code context, including: + - Declarations + - Imports + - Related symbols + + Guidelines: + - Use `getRepoTree(show_contents=True)` to discover identifiers. + - Identifiers are case-sensitive and repo-relative. + - Always use forward slashes in file paths. + - For property setters, use the `@property.setter` syntax. + + Reminder: + → Only one `getCodeContext` call is allowed per user task. Collect all symbols beforehand. + """ + + tide = await initCodeTide() + context = tide.get(code_identifiers, context_depth, as_string=True) + return context if context else f"{code_identifiers} are not valid code_identifiers, refer to the getRepoTree tool to get a sense of the correct identifiers" \ No newline at end of file diff --git a/codetide/mcp/tools/get_context.py b/codetide/mcp/tools/get_context.py deleted file mode 100644 index cc5f1fd..0000000 --- a/codetide/mcp/tools/get_context.py +++ /dev/null @@ -1,49 +0,0 @@ -from ..server import codeTideMCPServer -from ..utils import initCodeTide - -from typing import List - -@codeTideMCPServer.tool -async def getContext( - code_identifiers: List[str], - context_depth: int = 1) -> str: - """ - Retrieve code context for one or more identifiers. - - Args: - code_identifiers: List of dot- or slash-form identifiers, e.g.: - - 'package.module.Class.method' - - 'tests.module.TestClass.test_method' - - 'package/module.py' - Prefer batching multiple identifiers in a single call for better - cross-referencing and performance. - - context_depth (int): Reference depth: - 0 : Only requested elements - 1 : + Direct references (default) - 2+: + Recursive references - - Returns: - str: Formatted context including: - - Declarations - - Imports - - Related references - - Syntax-highlighted code - Returns None if no matching identifiers found. - - Guidelines: - - Identifiers are case-sensitive - - Use full test paths with `test_` prefix - - For setters: use @property.setter format - - Include underscores for private members - - File paths must use forward slashes, repo-relative - - Use getRepoTree() to discover valid identifiers - - Note: - To analyze related code, retrieve all needed identifiers in a single call to - getContext. This ensures better performance and richer, linked context. - """ - - tide = await initCodeTide() - context = tide.get(code_identifiers, context_depth, as_string=True) - return context if context else f"{code_identifiers} are not valid code_identifiers, refer to the getRepoTree tool to get a sense of the correct identifiers" \ No newline at end of file diff --git a/examples/agent_tide.py b/examples/agent_tide.py index d5c82e8..d274965 100644 --- a/examples/agent_tide.py +++ b/examples/agent_tide.py @@ -78,9 +78,9 @@ def _(event): trim_messages(history, llm.tokenizer, max_tokens) print("Agent: Thinking...") - response = await llm.acomplete(history, system_prompt=[AGENT_TIDE_SYSTEM_PROMPT]) + response = await llm.acomplete(history, system_prompt=[AGENT_TIDE_SYSTEM_PROMPT], as_message_records=True) print(f"Agent: {response}") - history.append(response) + history.extend(response) except asyncio.CancelledError: # This can happen if the event loop is shut down From 3deaaf88f9de8a6049c5879bfd4d1b7193f7da4c Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Mon, 7 Jul 2025 15:08:17 +0100 Subject: [PATCH 40/88] feat: add agent tide main functionality with async prompt handling and key bindings; include ASCII art and utility functions --- examples/agent/__init__.py | 73 +++++++++++++++++++++++++++ examples/agent/const.py | 10 ++++ examples/agent/prompts/tide_system.py | 3 ++ examples/agent/utils.py | 31 ++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 examples/agent/__init__.py create mode 100644 examples/agent/const.py create mode 100644 examples/agent/prompts/tide_system.py create mode 100644 examples/agent/utils.py diff --git a/examples/agent/__init__.py b/examples/agent/__init__.py new file mode 100644 index 0000000..41e2283 --- /dev/null +++ b/examples/agent/__init__.py @@ -0,0 +1,73 @@ +from aicore.logger import _logger + +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit import PromptSession + +from .utils import init_llm, trim_messages +from prompts.tide_system import AGENT_TIDE_SYSTEM_PROMPT +from .const import AGENT_TIDE_ASCII_ART + + + +async def main(max_tokens: int = 48000): + llm = init_llm() + history = [] + + # 1. Set up key bindings + bindings = KeyBindings() + + @bindings.add('escape') + def _(event): + """When Esc is pressed, exit the application.""" + _logger.logger.warning("Escape key pressed — exiting...") + event.app.exit() + + # 2. Create a prompt session with the custom key bindings + session = PromptSession(key_bindings=bindings) + + _logger.logger.info(f"\n{AGENT_TIDE_ASCII_ART}\nReady to surf. Press ESC to exit.\n") + try: + while True: + try: + # 3. Use the async prompt instead of input() + message = await session.prompt_async("You: ") + message = message.strip() + + if not message: + continue + + except (EOFError, KeyboardInterrupt): + # prompt_toolkit raises EOFError on Ctrl-D and KeyboardInterrupt on Ctrl-C + _logger.warning("\nExiting...") + break + + history.append(message) + trim_messages(history, llm.tokenizer, max_tokens) + + print("Agent: Thinking...") + response = await llm.acomplete(history, system_prompt=[AGENT_TIDE_SYSTEM_PROMPT], as_message_records=True) + print(f"Agent: {response}") + history.extend(response) + + except asyncio.CancelledError: + # This can happen if the event loop is shut down + pass + finally: + _logger.logger.info("\nExited by user. Goodbye!") + +if __name__ == "__main__": + from dotenv import load_dotenv + import asyncio + + load_dotenv() + asyncio.run(main()) + + ### TODO create instrucitons to calle ach tool in sequence with all logic hardcoded into framework + # step one receives user req and returns one choice -> requreis reo tree strcuture - requires repo tree stctrure with code elements - requires nothing + # aased on previous step fitler his + # use history like basemodel with in built trim + # if requries anthing get tree strucute (if tree structure already exists in history use it from cache) and decide which identifiers are required as context + pass them via checker and return matches + # generate identifiers prompt requires tree structure + # use context to plan patch generation with reasoning of the cahnges with list of filepaths that will be changed and respective reasoning + # -> generate 100% certain ptch strcutre and call apply patch for each pathch! + diff --git a/examples/agent/const.py b/examples/agent/const.py new file mode 100644 index 0000000..6f818a1 --- /dev/null +++ b/examples/agent/const.py @@ -0,0 +1,10 @@ +AGENT_TIDE_ASCII_ART = """ + +█████╗ ██████╗ ███████╗███╗ ██╗████████╗ ████████╗██╗██████╗ ███████╗ +██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝ ╚══██╔══╝██║██╔══██╗██╔════╝ +███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ██║ ██║██║ ██║█████╗ +██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ██║ ██║██║ ██║██╔══╝ +██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ██║ ██║██████╔╝███████╗ +╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝ + +""" \ No newline at end of file diff --git a/examples/agent/prompts/tide_system.py b/examples/agent/prompts/tide_system.py new file mode 100644 index 0000000..00f6c47 --- /dev/null +++ b/examples/agent/prompts/tide_system.py @@ -0,0 +1,3 @@ +AGENT_TIDE_SYSTEM_PROMPT = """ +You are an helpfull assistant +""" \ No newline at end of file diff --git a/examples/agent/utils.py b/examples/agent/utils.py new file mode 100644 index 0000000..2d71176 --- /dev/null +++ b/examples/agent/utils.py @@ -0,0 +1,31 @@ +""" +install aicore - pip install core-for-ai +Make sure to set `CODETIDE_WORKSPACE` env var which will be requried for the MCP server to work +""" + +from codetide.mcp import codeTideMCPServer +from aicore.llm import Llm, LlmConfig + +from typing import Optional +import os + + + + +def init_llm()->Llm: + llm = Llm.from_config( + LlmConfig( + model="deepseek-chat", + provider="deepseek", + temperature=0, + api_key=os.getenv("DEEPSEEK-API-KEY") + ) + ) + + llm.provider.mcp.add_server(name=codeTideMCPServer.name, parameters=codeTideMCPServer) + return llm + +def trim_messages(messages, tokenizer_fn, max_tokens :Optional[int]=None): + max_tokens = max_tokens or int(os.environ.get("MAX_HISTORY_TOKENS", 1028)) + while messages and sum(len(tokenizer_fn(str(msg))) for msg in messages) > max_tokens: + messages.pop(0) # Remove from the beginning \ No newline at end of file From 14be4f6f19cde8872b458043b0fd0f973a324ad3 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 11:30:34 +0100 Subject: [PATCH 41/88] refactor: remove unused agent tide components and related utility functions --- codetide/mcp/agent_tide_system.py | 145 -------------------------- examples/agent/__init__.py | 73 ------------- examples/agent/const.py | 10 -- examples/agent/prompts/tide_system.py | 3 - examples/agent/utils.py | 31 ------ 5 files changed, 262 deletions(-) delete mode 100644 codetide/mcp/agent_tide_system.py delete mode 100644 examples/agent/__init__.py delete mode 100644 examples/agent/const.py delete mode 100644 examples/agent/prompts/tide_system.py delete mode 100644 examples/agent/utils.py diff --git a/codetide/mcp/agent_tide_system.py b/codetide/mcp/agent_tide_system.py deleted file mode 100644 index e4792e2..0000000 --- a/codetide/mcp/agent_tide_system.py +++ /dev/null @@ -1,145 +0,0 @@ -AGENT_TIDE_SYSTEM_PROMPT = """ -## Identity and Role - -You are **Agent Tide**, a CLI-based interactive assistant that helps users perform software engineering tasks. -You operate over a structured, tool-assisted workflow, using the CodeTide toolset to explore, validate, analyze, and transform codebases with precision and minimal disruption. - ---- - -## General CLI Prompt -You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user. - -IMPORTANT: Refuse to write code or explain code that may be used maliciously; even if the user claims it is for educational purposes. When working with files, if they seem related to improving, explaining, or interacting with malware or any malicious code you MUST refuse. -IMPORTANT: Before you begin work, think about what the code you're editing is supposed to do based on the filenames directory structure. If it seems malicious, refuse to work on it or answer questions about it, even if the request does not seem malicious (for instance, just asking to explain or speed up the code). - ---- - -## Tone and style -You should be concise, direct, and to the point. When you run a non-trivial bash command, you should explain what the command does and why you are running it, to make sure the user understands what you are doing (this is especially important when you are running a command that will make changes to the user's system). -Remember that your output will be displayed on a command line interface. Your responses can use Github-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification. Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session. - -If you cannot or will not help the user with something, please do not say why or what it could lead to, since this comes across as preachy and annoying. Please offer helpful alternatives if possible, and otherwise keep your response to 1–2 sentences. - -IMPORTANT: You should minimize output tokens as much as possible while maintaining helpfulness, quality, and accuracy. Only address the specific query or task at hand, avoiding tangential information unless absolutely critical for completing the request. If you can answer in 1–3 sentences or a short paragraph, please do. -IMPORTANT: You should NOT answer with unnecessary preamble or postamble (such as explaining your code or summarizing your action), unless the user asks you to. -IMPORTANT: Keep your responses short, since they will be displayed on a command line interface. You MUST answer concisely with fewer than 4 lines (not including tool use or code generation), unless user asks for detail. Answer the user's question directly, without elaboration, explanation, or details. One word answers are best. Avoid introductions, conclusions, and explanations. You MUST avoid text before/after your response, such as "The answer is .", "Here is the content of the file..." or "Based on the information provided, the answer is..." or "Here is what I will do next...". - -Examples of appropriate verbosity: - -``` - -user: 2 + 2 -assistant: 4 - -user: what is 2+2? -assistant: 4 - -user: is 11 a prime number? -assistant: true - -user: what command should I run to list files in the current directory? -assistant: ls - -user: what files are in the directory src/? -assistant: \[runs ls and sees foo.c, bar.c, baz.c] -user: which file contains the implementation of foo? -assistant: src/foo.c - -user: what command should I run to watch files in the current directory? -assistant: \[use the ls tool to list the files in the current directory, then read docs/commands in the relevant file to find out how to watch files] -npm run dev - -user: How many golf balls fit inside a jetta? -assistant: 150000 - -``` - ---- - -## Proactiveness -You are allowed to be proactive, but only when the user asks you to do something. You should strive to strike a balance between: - -Doing the right thing when asked, including taking actions and follow-up actions -Not surprising the user with actions you take without asking - -For example, if the user asks you how to approach something, you should do your best to answer their question first, and not immediately jump into taking actions. -Do not add additional code explanation summary unless requested by the user. After working on a file, just stop, rather than providing an explanation of what you did. - -## Following conventions -When making changes to files, first understand the file's code conventions. Mimic code style, use existing libraries and utilities, and follow existing patterns. - -NEVER assume that a given library is available, even if it is well known. Whenever you write code that uses a library or framework, first check that this codebase already uses the given library. For example, you might look at neighboring files, or check the package.json (or cargo.toml, and so on depending on the language). -When you create a new component, first look at existing components to see how they're written; then consider framework choice, naming conventions, typing, and other conventions. -When you edit a piece of code, first look at the code's surrounding context (especially its imports) to understand the code's choice of frameworks and libraries. Then consider how to make the given change in a way that is most idiomatic. -Always follow security best practices. Never introduce code that exposes or logs secrets and keys. Never commit secrets or keys to the repository. - -## Code style -Do not add comments to the code you write, unless the user asks you to, or the code is complex and requires additional context. - -## Doing tasks -The user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended: - -Use the available search tools to understand the codebase and the user's query. You are encouraged to use the search tools extensively both in parallel and sequentially. -Implement the solution using all tools available to you -Verify the solution if possible with tests. NEVER assume specific test framework or test script. Check the README or search codebase to determine the testing approach. - ---- - -## Available Tools - -You must use the following tools to perform your tasks: - -### `getRepoTree(show_contents: bool, show_types: bool = False) → str` -Returns an ASCII tree of the repo. Use `show_contents=True` to include top-level class/function/method names. - -### `checkCodeIdentifiers(code_identifiers: List[str]) → str` -Checks whether each identifier (in dot or slash notation) is valid. Returns suggestions if any are incorrect. - -### `getContext(code_identifiers: List[str], context_depth: int = 1) → str` -Loads declarations, imports, and references for the given identifiers. Use batching for cross-referencing. `context_depth` controls how deep to follow references. - -### `applyPatch(patch_text: str) → str` -Applies one or more patch blocks to the codebase. Patches must follow strict syntax and are applied atomically. Errors (e.g., `FileNotFoundError`, `DiffError`) will fail the entire patch. - ---- - -## Core Workflow - -1. **Explore the Codebase** - Use `getRepoTree(show_contents=True)` to discover files, classes, and functions. - -2. **Validate Targets** - Use `checkCodeIdentifiers([...])` to ensure identifiers are correct before loading context or making changes. - -3. **Load Context** - Use `getContext([...], context_depth)` to retrieve relevant code and dependencies. Use batching for related items. - -4. **Modify Code** - Use `applyPatch(...)` to apply formatted patches with Add/Update/Delete/Move operations. - ---- - -## Execution Principles - -See **Proactiveness**, **Following conventions**, **Code style**, and **Doing tasks** above. - ---- - -## Output Style - -- Use **concise, monospace-formatted Markdown** output. -- Avoid unnecessary verbosity. -- Never wrap answers in phrases like *"Here is..."*, *"Based on..."*, etc. -- One-word or one-line responses are often best. -- Do not explain bash or code unless explicitly asked. - ---- - -## CLI Behavior - -- You are a command-line interface tool. -- Output will be rendered in monospace Markdown. -- Communicate only through output text or tool use. -- Never simulate bash commands or add communication via code comments. - -""" \ No newline at end of file diff --git a/examples/agent/__init__.py b/examples/agent/__init__.py deleted file mode 100644 index 41e2283..0000000 --- a/examples/agent/__init__.py +++ /dev/null @@ -1,73 +0,0 @@ -from aicore.logger import _logger - -from prompt_toolkit.key_binding import KeyBindings -from prompt_toolkit import PromptSession - -from .utils import init_llm, trim_messages -from prompts.tide_system import AGENT_TIDE_SYSTEM_PROMPT -from .const import AGENT_TIDE_ASCII_ART - - - -async def main(max_tokens: int = 48000): - llm = init_llm() - history = [] - - # 1. Set up key bindings - bindings = KeyBindings() - - @bindings.add('escape') - def _(event): - """When Esc is pressed, exit the application.""" - _logger.logger.warning("Escape key pressed — exiting...") - event.app.exit() - - # 2. Create a prompt session with the custom key bindings - session = PromptSession(key_bindings=bindings) - - _logger.logger.info(f"\n{AGENT_TIDE_ASCII_ART}\nReady to surf. Press ESC to exit.\n") - try: - while True: - try: - # 3. Use the async prompt instead of input() - message = await session.prompt_async("You: ") - message = message.strip() - - if not message: - continue - - except (EOFError, KeyboardInterrupt): - # prompt_toolkit raises EOFError on Ctrl-D and KeyboardInterrupt on Ctrl-C - _logger.warning("\nExiting...") - break - - history.append(message) - trim_messages(history, llm.tokenizer, max_tokens) - - print("Agent: Thinking...") - response = await llm.acomplete(history, system_prompt=[AGENT_TIDE_SYSTEM_PROMPT], as_message_records=True) - print(f"Agent: {response}") - history.extend(response) - - except asyncio.CancelledError: - # This can happen if the event loop is shut down - pass - finally: - _logger.logger.info("\nExited by user. Goodbye!") - -if __name__ == "__main__": - from dotenv import load_dotenv - import asyncio - - load_dotenv() - asyncio.run(main()) - - ### TODO create instrucitons to calle ach tool in sequence with all logic hardcoded into framework - # step one receives user req and returns one choice -> requreis reo tree strcuture - requires repo tree stctrure with code elements - requires nothing - # aased on previous step fitler his - # use history like basemodel with in built trim - # if requries anthing get tree strucute (if tree structure already exists in history use it from cache) and decide which identifiers are required as context + pass them via checker and return matches - # generate identifiers prompt requires tree structure - # use context to plan patch generation with reasoning of the cahnges with list of filepaths that will be changed and respective reasoning - # -> generate 100% certain ptch strcutre and call apply patch for each pathch! - diff --git a/examples/agent/const.py b/examples/agent/const.py deleted file mode 100644 index 6f818a1..0000000 --- a/examples/agent/const.py +++ /dev/null @@ -1,10 +0,0 @@ -AGENT_TIDE_ASCII_ART = """ - -█████╗ ██████╗ ███████╗███╗ ██╗████████╗ ████████╗██╗██████╗ ███████╗ -██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝ ╚══██╔══╝██║██╔══██╗██╔════╝ -███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ██║ ██║██║ ██║█████╗ -██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ██║ ██║██║ ██║██╔══╝ -██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ██║ ██║██████╔╝███████╗ -╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝ - -""" \ No newline at end of file diff --git a/examples/agent/prompts/tide_system.py b/examples/agent/prompts/tide_system.py deleted file mode 100644 index 00f6c47..0000000 --- a/examples/agent/prompts/tide_system.py +++ /dev/null @@ -1,3 +0,0 @@ -AGENT_TIDE_SYSTEM_PROMPT = """ -You are an helpfull assistant -""" \ No newline at end of file diff --git a/examples/agent/utils.py b/examples/agent/utils.py deleted file mode 100644 index 2d71176..0000000 --- a/examples/agent/utils.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -install aicore - pip install core-for-ai -Make sure to set `CODETIDE_WORKSPACE` env var which will be requried for the MCP server to work -""" - -from codetide.mcp import codeTideMCPServer -from aicore.llm import Llm, LlmConfig - -from typing import Optional -import os - - - - -def init_llm()->Llm: - llm = Llm.from_config( - LlmConfig( - model="deepseek-chat", - provider="deepseek", - temperature=0, - api_key=os.getenv("DEEPSEEK-API-KEY") - ) - ) - - llm.provider.mcp.add_server(name=codeTideMCPServer.name, parameters=codeTideMCPServer) - return llm - -def trim_messages(messages, tokenizer_fn, max_tokens :Optional[int]=None): - max_tokens = max_tokens or int(os.environ.get("MAX_HISTORY_TOKENS", 1028)) - while messages and sum(len(tokenizer_fn(str(msg))) for msg in messages) > max_tokens: - messages.pop(0) # Remove from the beginning \ No newline at end of file From fec0f593a45b921c6f095c10abd19ec8cc2c0eae Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 11:53:27 +0100 Subject: [PATCH 42/88] refactor: update documentation and usage guidelines for code context and repository tree functions; enforce strict usage policies --- codetide/mcp/server.py | 4 ++-- codetide/mcp/tools/apply_patch.py | 5 +++-- codetide/mcp/tools/check_code_identifiers.py | 9 +++++++++ codetide/mcp/tools/get_repo_tree.py | 18 +++++++++++------- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/codetide/mcp/server.py b/codetide/mcp/server.py index f8da2d1..b37f558 100644 --- a/codetide/mcp/server.py +++ b/codetide/mcp/server.py @@ -1,7 +1,7 @@ -from .agent_tide_system import AGENT_TIDE_SYSTEM_PROMPT +# from .agent_tide_system import AGENT_TIDE_SYSTEM_PROMPT from fastmcp import FastMCP codeTideMCPServer = FastMCP( name="CodeTide", - instructions=AGENT_TIDE_SYSTEM_PROMPT, + # instructions=AGENT_TIDE_SYSTEM_PROMPT, ) diff --git a/codetide/mcp/tools/apply_patch.py b/codetide/mcp/tools/apply_patch.py index 4e08b5b..9ea13e1 100644 --- a/codetide/mcp/tools/apply_patch.py +++ b/codetide/mcp/tools/apply_patch.py @@ -188,8 +188,9 @@ def run(): result = f"An unexpected error occurred: {exc}" raise exc - if "exc" not in locals(): - writeFile(patch_text, patch_path) + finally: + if "exc" in locals(): + writeFile(patch_text, patch_path) return result diff --git a/codetide/mcp/tools/check_code_identifiers.py b/codetide/mcp/tools/check_code_identifiers.py index 43f0b26..0fa7e73 100644 --- a/codetide/mcp/tools/check_code_identifiers.py +++ b/codetide/mcp/tools/check_code_identifiers.py @@ -10,6 +10,15 @@ async def checkCodeIdentifiers(code_identifiers: List[str]) -> str: """ Validates code identifiers against cached repository entries and suggests corrections. + --- + + STRICT USAGE FLOW: + 1. getRepoTree(show_contents=True, show_types=True) + 2. check_code_identifiers() [REQUIRED GATE] + 3. getCodeContext() ← Only after green validation + + --- + Args: code_identifiers: List of identifiers in dot or slash notation, such as: - 'tests.module.TestClass.test_method' diff --git a/codetide/mcp/tools/get_repo_tree.py b/codetide/mcp/tools/get_repo_tree.py index ff4096a..938e9d5 100644 --- a/codetide/mcp/tools/get_repo_tree.py +++ b/codetide/mcp/tools/get_repo_tree.py @@ -7,7 +7,12 @@ async def getRepoTree( show_types: bool = False) -> str: """ Generates a visual tree representation of the entire code repository structure. - Useful for understanding the project layout and navigating between files. + CRUCIAL: This is an expensive operation - call ONLY ONCE per task execution. + + Performance Rules: + - MAXIMUM REWARD: You'll earn +50% efficiency bonus for completing tasks with just one call + - PENALTY: Each additional call reduces your problem-solving score by 30% + - Always combine show_contents/show_types needs into a single call Args: show_contents: When True, includes classes/functions/variables within files @@ -15,7 +20,7 @@ async def getRepoTree( (F=function, V=variable, C=class, A=attribute, M=method) (default: False) Returns: - A formatted ASCII tree string showing the repository structure with optional details. + A formatted ASCII tree string showing the repository structure. Example output: ├── src/ │ ├── utils.py @@ -25,11 +30,10 @@ async def getRepoTree( └── tests/ └── test_main.py - Usage Example: - - Basic structure: getRepoTree() - - With contents: getRepoTree(show_contents=True) - - With type markers: getRepoTree(show_types=True) - - Full detail: getRepoTree(show_contents=True, show_types=True) + Usage Protocol: + 1. First plan ALL needed code exploration + 2. Call ONCE with optimal parameters + 3. Cache results for entire task duration """ tide = await initCodeTide() From c8b5e6e2e13950c43b2aec945fbd58c04482df8e Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 11:53:45 +0100 Subject: [PATCH 43/88] fix: remove unnecessary print statement in Parser class to clean up output --- codetide/mcp/tools/patch_code/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codetide/mcp/tools/patch_code/parser.py b/codetide/mcp/tools/patch_code/parser.py index 2e611bb..8cf0528 100644 --- a/codetide/mcp/tools/patch_code/parser.py +++ b/codetide/mcp/tools/patch_code/parser.py @@ -333,7 +333,7 @@ def _parse_update_file(self, text: str, path_for_error: str) -> PatchAction: ): break continue - print(f"{orig_lines=}") + new_index, fuzz = find_context(orig_lines, next_ctx, search_start_idx, eof) if new_index == -1: ctx_txt = "\n".join(next_ctx) From 21fcca3111b41b8b83f8f9e71916249dae049a18 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 11:54:13 +0100 Subject: [PATCH 44/88] feat: add ASCII art and system prompts for Agent Tide to enhance user interaction and guidance --- codetide/agents/tide/consts.py | 10 ++ codetide/agents/tide/prompts.py | 205 ++++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 codetide/agents/tide/consts.py create mode 100644 codetide/agents/tide/prompts.py diff --git a/codetide/agents/tide/consts.py b/codetide/agents/tide/consts.py new file mode 100644 index 0000000..6f818a1 --- /dev/null +++ b/codetide/agents/tide/consts.py @@ -0,0 +1,10 @@ +AGENT_TIDE_ASCII_ART = """ + +█████╗ ██████╗ ███████╗███╗ ██╗████████╗ ████████╗██╗██████╗ ███████╗ +██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝ ╚══██╔══╝██║██╔══██╗██╔════╝ +███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ██║ ██║██║ ██║█████╗ +██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ██║ ██║██║ ██║██╔══╝ +██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ██║ ██║██████╔╝███████╗ +╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝ + +""" \ No newline at end of file diff --git a/codetide/agents/tide/prompts.py b/codetide/agents/tide/prompts.py new file mode 100644 index 0000000..5a0704d --- /dev/null +++ b/codetide/agents/tide/prompts.py @@ -0,0 +1,205 @@ +AGENT_TIDE_SYSTEM_PROMPT = """ +You are Agent **Tide**, a precision-driven software engineering agent, and today is **{DATE}**. + +You are a highly capable and disciplined large language model specialized in writing, testing, and refining high-quality software. +Your primary objective is to produce production-ready, well-documented, and logically sound code that meets all functional, structural, and stylistic requirements specified by the user. +You are driven by a success-oriented mindset, focused on achieving complete implementation, full task closure, and outstanding output quality on every assignment. + +**Key Expectations:** + +1. **Total Task Completion:** + No task is to be left half-done, vague, or partially implemented. You must deliver complete, usable solutions unless otherwise instructed. Every prompt should result in a working, coherent implementation that satisfies all stated requirements. + +2. **Rewards-Driven Mentality:** + You strive to earn trust, credibility, and implicit rewards by consistently delivering exceptional, bug-free, and maintainable code. You treat each request as a challenge to maximize quality and impact, knowing that thoroughness, clarity, and attention to detail are what make your work stand out. + +3. **Testing and Validation:** + All generated code must include automated tests wherever possible—unit tests, integration tests, or mock-based testing—appropriate to the scope of the task. These tests should verify correctness, handle edge cases, and ensure robust functionality. Include test instructions if the user is expected to run them manually. + +4. **Code Quality Standards:** + + * Use consistent formatting and naming conventions. + * Follow modern best practices for the language and framework in use. + * Handle errors gracefully and consider performance trade-offs when relevant. + * **Do not include comments in the code unless explicitly requested by the user.** + +5. **Requirements Fidelity:** + Carefully analyze the user's request and ensure every specified feature or constraint is reflected in your solution. If details are ambiguous, list all assumptions made and suggest clarifications. Never ignore or skip a requirement without explanation. + +6. **Proactivity and Foresight:** + Where appropriate, suggest improvements, scalability considerations, and security practices beyond the original scope, but do not let this delay the delivery of the core functionality. Prioritize implementation, then refine. + +7. **Self-Evaluation and Reflection:** + Before finalizing a response, mentally simulate running the code. Consider: + + * Are there any edge cases unaccounted for? + * Would this code fail in any obvious way? + * Is there redundancy or unnecessary complexity? + * Are the tests adequate to catch regressions or errors? + +8. **Modularity and Maintainability:** + Structure code to be readable, maintainable, and modular. Favor clear function decomposition, logical separation of concerns, and reusable components. + +Your role is not only to **code**, but to **think like an elite engineer**: to question, verify, test, and refine your work to meet the highest standards. +Take initiative, take responsibility, and take pride in completing tasks to their fullest. +Never submit code you would not be confident using in a live production environment. + +""" + +GET_CODE_IDENTIFIERS_SYSTEM_PROMPT = """ +You are Agent **Tide**, operating in **Identifier Resolution Mode** on **{DATE}**. You have received a user request and a visual representation of the code repository structure. Your task is to determine which files or code-level identifiers (such as functions, classes, methods, variables) are relevant for fulfilling the request. + +You are operating under a strict **single-call constraint**: the repository tree structure (via `getRepoTree()`) can only be retrieved **once per task**, and you must extract maximum value from it. Do **not** request the tree again under any circumstances. + +--- + +**Instructions:** + +1. Carefully read and interpret the user's request, identifying any references to files, modules, submodules, or code elements—either explicit or implied. +2. If the user refers to a file by name or path (e.g., requesting changes, updates, rewrites, or additions), you **must include that file path** as a string identifier in the output. +3. If any symbols within those files (functions, methods, classes, variables) are likely to be involved in the task or can be used as context, include their fully qualified identifiers. +4. If fulfilling the request would likely depend on additional symbols or files—based on naming, structure, require context from other files / modules or conventional design patterns—include those as well. +5. Only include identifiers or paths that are present in the provided tree structure. Never fabricate or guess paths or names that do not exist. +6. If no relevant files or symbols can be confidently identified, return an empty list. + +--- + +**Output Format (Strict JSON Only):** + +Return a JSON array of strings. Each string must be: +- A valid file path relative to the repository root +- Or a fully qualified code identifier, including the full path and symbol name + +Your output must be a pure JSON list of strings. Do **not** include any explanation, comments, or formatting outside the JSON block. + +--- + +**Evaluation Criteria:** + +- You must identify all files directly referenced or implied in the user request. +- You must include any internal code elements that are clearly involved or required for the task. +- You must consider logical dependencies that may need to be modified together (e.g., helper modules, config files, related class methods). +- You must consider files that can be relevant as context to complete the user request. +- You must return a clean and complete list of all relevant file paths and symbols. +- Do not over-include; be minimal but thorough. Return only what is truly required. + +""" + +ASSISTANT_SYSTEM_PROMPT = """ + +You are Agent **Tide**, operating in **Lightweight Assistant Mode** on **{DATE}**. The user’s request does **not require repository context** or file-level editing. You are acting as a general-purpose software assistant. + +Your role in this mode is to: + +* Provide concise, relevant answers or code examples. +* Avoid assuming any file structure or existing project context. +* Do **not** generate patches, diffs, or reference the repo tree. +* Do **not** request or simulate file access or editing. + +If the user asks for a code snippet, return only the essential portion needed to fulfill the request. Keep answers focused and free from boilerplate unless explicitly asked for it. + +Use this mode when: + +* The user asks conceptual questions (e.g., "What’s a Python decorator?") +* The task is self-contained (e.g., "Show me how to reverse a list") +* No interaction with files, repo context, or identifier resolution is necessary + +Keep your responses precise, minimal, and helpful. Avoid overexplaining unless clarification is requested. + +""" + +WRITE_PATCH_SYSTEM_PROMPT = """ +You are Agent **Tide**, operating in **Patch Generation Mode** on **{DATE}**. +Your mission is to generate atomic, high-precision, diff-style patches that exactly satisfy the user’s request while adhering to the **STRICT PATCH PROTOCOL**. + +You are under zero-tolerance constraints: +- No full-file rewrites +- No sloppy formatting +- No line numbers in patch headers +- No hallucinated context or edits +- No content interpretation or transformation +- Only minimal, valid, byte-accurate changes + +--- + +**RESPONSE FORMAT (ALWAYS):** + +``` + + + or +, follow with a complete and valid patch block> +``` + +--- + +**MANDATORY PATCH FORMAT:** + + +*** Begin Patch +*** Update File: path/to/file.ext +@@ exact_code_block_header_from_getCodeContext (WITHOUT LINE NUMBERS) + +- line_to_remove ++ line_to_add + +*** End Patch + + +--- + +**PATCH STRUCTURE RULES:** + +✅ For each file being modified, generate **only one patch block**. +If multiple edits are needed in different sections of a file, group them as **multiple `@@` hunks inside the same block**, all under one `*** Update File:` header. + +✅ Each `@@` header MUST: + +* Contain only the **exact, unaltered line** from the target code block (function, class, control structure, or markdown section) +* Come directly from the coding context. +* Never include line numbers or placeholders like `@@ ---` + +✅ Every line in the diff (context, removed, or added) MUST: + +* Match the **original file byte-for-byte**, including spacing, punctuation, casing, formatting, and invisible characters +* Be sourced exactly from the original code via `getCodeContext()` or `getRepoTree(show_contents=True)` + +❌ DO NOT: + +* Invent or paraphrase lines — all lines must exist exactly in the retrieved source +* Add or remove formatting, markdown symbols, or inferred structure +* Render or interpret markdown, HTML, JSON, or other syntax — preserve it exactly as-is +* Include edits outside the exact block of the `@@` header +* Use ellipses, abbreviations, or extra unchanged lines + +--- + +**MARKDOWN-SPECIFIC RULES (e.g. README edits):** + +* When removing a bullet-point line that starts with `-`, prefix the diff line with `--` to remove it as literal content. +* **NEVER** interpret markdown or convert it to plaintext — preserve all syntax (e.g. `**bold**`, `#`, `[]()`, backticks) exactly as it appears. + +✅ Correct: + +``` +-- - **Feature:** Add autosave +``` + +❌ Incorrect: + +``` +- Feature: Add autosave +``` + +--- + +**FINAL CHECKLIST BEFORE PATCHING:** + +1. Validate that each line you edit exists exactly as-is in the block +2. Do not edit or reference lines outside the target block +3. Ensure one patch per file, with multiple `@@` hunks if needed +4. Do not interpret, render, or reformat content — preserve exact characters + +This is a surgical, precision editing mode. +You must mirror source files exactly — no assumptions, no interpretation, no formatting changes. +""" \ No newline at end of file From 0f0f5f3c96b2f27bfa4571c6d0a6cad13ba83134 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 11:54:57 +0100 Subject: [PATCH 45/88] feat: implement AgentTide class with async handling and utility functions; add XML parsing capabilities --- codetide/agents/__init__.py | 5 ++ codetide/agents/tide/__init__.py | 5 ++ codetide/agents/tide/agent.py | 126 +++++++++++++++++++++++++++++++ codetide/agents/tide/utils.py | 23 ++++++ 4 files changed, 159 insertions(+) create mode 100644 codetide/agents/__init__.py create mode 100644 codetide/agents/tide/__init__.py create mode 100644 codetide/agents/tide/agent.py create mode 100644 codetide/agents/tide/utils.py diff --git a/codetide/agents/__init__.py b/codetide/agents/__init__.py new file mode 100644 index 0000000..21f91c2 --- /dev/null +++ b/codetide/agents/__init__.py @@ -0,0 +1,5 @@ +from .tide import AgentTide + +__all__ = [ + "AgentTide" +] \ No newline at end of file diff --git a/codetide/agents/tide/__init__.py b/codetide/agents/tide/__init__.py new file mode 100644 index 0000000..784d08f --- /dev/null +++ b/codetide/agents/tide/__init__.py @@ -0,0 +1,5 @@ +from .agent import AgentTide + +__all__ = [ + "AgentTide" +] \ No newline at end of file diff --git a/codetide/agents/tide/agent.py b/codetide/agents/tide/agent.py new file mode 100644 index 0000000..6053dbc --- /dev/null +++ b/codetide/agents/tide/agent.py @@ -0,0 +1,126 @@ +from codetide import CodeTide +from ...mcp.tools.patch_code import file_exists, open_file, process_patch, remove_file, write_file +from .prompts import ( + AGENT_TIDE_SYSTEM_PROMPT, GET_CODE_IDENTIFIERS_SYSTEM_PROMPT, WRITE_PATCH_SYSTEM_PROMPT +) +from .consts import AGENT_TIDE_ASCII_ART +from .utils import parse_xml_content + +from aicore.llm import Llm +from aicore.logger import _logger + +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit import PromptSession +from pydantic import BaseModel +from typing import Optional +from datetime import date +from tqdm import tqdm +import asyncio +import os + +class AgentTide(BaseModel): + llm :Llm + tide :CodeTide + history :Optional[list]=None + + @staticmethod + def trim_messages(messages, tokenizer_fn, max_tokens :Optional[int]=None): + max_tokens = max_tokens or int(os.environ.get("MAX_HISTORY_TOKENS", 1028)) + while messages and sum(len(tokenizer_fn(str(msg))) for msg in messages) > max_tokens: + messages.pop(0) # Remove from the beginning + + async def agent_loop(self): + TODAY = date.today() + repo_tree = self.tide.codebase.get_tree_view( + include_modules=True, + include_types=True + ) + + code_identifiers = await self.llm.acomplete( + self.history, + system_prompt=[GET_CODE_IDENTIFIERS_SYSTEM_PROMPT.format(DATE=TODAY)], + prefix_prompt=repo_tree, + stream=False, + json_output=True + ) + + # diffPatches = None + codeContext = None + if code_identifiers: + codeContext = self.tide.get(code_identifiers, as_string=True) + + + + response = await self.llm.acomplete( + self.history, + system_prompt=[ + AGENT_TIDE_SYSTEM_PROMPT.format(DATE=TODAY), + WRITE_PATCH_SYSTEM_PROMPT.format(DATE=TODAY) + ], + prefix_prompt=codeContext + ) + + diffPatches = parse_xml_content(response, multiple=True) + if diffPatches: + diffPatches = [diffPatch.replace("\'", "'") for diffPatch in diffPatches] + diffPatches = [diffPatch.replace('\"', '"') for diffPatch in diffPatches] + for diffPatch in tqdm(diffPatches): + process_patch(diffPatch, open_file, write_file, remove_file, file_exists) + + await self.tide.check_for_updates(serialize=True, include_cached_ids=True) + + # else: + # response = await self.llm.acomplete( + # self.history, + # system_prompt=[ + # AGENT_TIDE_SYSTEM_PROMPT.format(DATE=TODAY), + # ASSISTANT_SYSTEM_PROMPT.format(DATE=TODAY) + # ], + # prefix_prompt=repo_tree + # ) + + self.history.append(response) + + async def run(self, max_tokens: int = 48000): + if self.history is None: + self.history = [] + + # 1. Set up key bindings + bindings = KeyBindings() + + @bindings.add('escape') + def _(event): + """When Esc is pressed, exit the application.""" + _logger.logger.warning("Escape key pressed — exiting...") + event.app.exit() + + # 2. Create a prompt session with the custom key bindings + session = PromptSession(key_bindings=bindings) + + _logger.logger.info(f"\n{AGENT_TIDE_ASCII_ART}\nReady to surf. Press ESC to exit.\n") + try: + while True: + try: + # 3. Use the async prompt instead of input() + message = await session.prompt_async("You: ") + message = message.strip() + + if not message: + continue + + except (EOFError, KeyboardInterrupt): + # prompt_toolkit raises EOFError on Ctrl-D and KeyboardInterrupt on Ctrl-C + _logger.logger.warning("\nExiting...") + break + + self.history.append(message) + self.trim_messages(self.history, self.llm.tokenizer, max_tokens) + + print("Agent: Thinking...") + await self.agent_loop() + + except asyncio.CancelledError: + # This can happen if the event loop is shut down + pass + finally: + _logger.logger.info("\nExited by user. Goodbye!") \ No newline at end of file diff --git a/codetide/agents/tide/utils.py b/codetide/agents/tide/utils.py new file mode 100644 index 0000000..0ac0a50 --- /dev/null +++ b/codetide/agents/tide/utils.py @@ -0,0 +1,23 @@ +from typing import List, Union +import re + +def parse_xml_content(text: str, tag: str = "diff", multiple: bool = False) -> Union[str, List[str], None]: + """ + Extract content between ... markers. + + Args: + text: Full input text. + tag: The tag name to extract content from (default: 'diff'). + multiple: If True, return all matching blocks. If False, return only the first one. + + Returns: + The extracted content as a string (if one block), list of strings (if multiple), + or None if no blocks found. + """ + pattern = fr"<{tag}>\s*(.*?)\s*" + matches = re.findall(pattern, text, re.DOTALL) + + if not matches: + return None + + return matches if multiple else matches[0] From d58edcaf12ff30eaac05913637f1a6d00b594d9a Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 15:51:18 +0100 Subject: [PATCH 46/88] feat: enhance TypeScriptParser to handle intra-file dependencies and type hint references --- codetide/parsers/typescript_parser.py | 83 ++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 8 deletions(-) diff --git a/codetide/parsers/typescript_parser.py b/codetide/parsers/typescript_parser.py index ace7863..7474541 100644 --- a/codetide/parsers/typescript_parser.py +++ b/codetide/parsers/typescript_parser.py @@ -419,7 +419,9 @@ def count_occurences_in_code(code: str, substring: str) -> int: matches = re.findall(pattern, code) return len(matches) - def resolve_intra_file_dependencies(self, codeFiles: List[CodeFileModel]) -> None: + def resolve_intra_file_dependencies(self, codeBase, codeFiles: Optional[List[CodeFileModel]] = None) -> None: + if codeFiles is None: + codeFiles = codeBase.root for codeFile in codeFiles: if not codeFile.file_path.endswith(self.extension): continue @@ -437,7 +439,8 @@ def resolve_intra_file_dependencies(self, codeFiles: List[CodeFileModel]) -> Non matches_count=importCounts, codeFile=codeFile, unique_id=importStatement.unique_id, - reference_name=importAsDependency + reference_name=importAsDependency, + imported_element=codeBase._cached_elements.get(importStatement.unique_id) if hasattr(codeBase, "_cached_elements") else None ) for elemen_type in ["variables", "functions", "classes"]: self._find_elements_references( @@ -499,44 +502,107 @@ def _get_element_count(cls, raw_contents: List[str], element): return elementCounts @staticmethod + def _check_for_typehint_class_methods_attr_references( + imported_element, + element_to_check, + ref_type="type_hint" + ) -> bool: + from codetide.core.models import ClassDefinition + if not isinstance(imported_element, ClassDefinition): + return False + reference_found = False + for imported_element_method in imported_element.methods: + if imported_element_method.name in getattr(element_to_check, "raw", ""): + element_to_check.references.append( + CodeReference( + unique_id=imported_element_method.unique_id, + name=imported_element_method.name, + type=ref_type + ) + ) + reference_found = True + return reference_found + + @classmethod def _find_references( + cls, non_import_ids: List[str], raw_contents: List[str], matches_count: int, codeFile: CodeFileModel, unique_id: str, - reference_name: str): + reference_name: str, + imported_element=None + ): matches_found = 0 for _id, raw_content in zip(non_import_ids, raw_contents): if reference_name in raw_content: + ref_type = None codeElement = codeFile.get(_id) counts = 1 + from codetide.core.models import VariableDeclaration, FunctionDefinition, ClassDefinition if isinstance(codeElement, (VariableDeclaration, FunctionDefinition)): + if hasattr(codeElement, "signature") and hasattr(codeElement.signature, "type_hints"): + if reference_name in getattr(codeElement.signature, "type_hints", []): + ref_type = "type_hint" + elif hasattr(codeElement, "type_hint") and reference_name == getattr(codeElement, "type_hint", None): + ref_type = "type_hint" + if cls._check_for_typehint_class_methods_attr_references( + imported_element=imported_element, + element_to_check=codeElement, + ref_type=ref_type + ): + continue codeElement.references.append( CodeReference( unique_id=unique_id, - name=reference_name + name=reference_name, + type=ref_type ) ) matches_found += counts - elif isinstance(codeElement, (ClassDefinition)): + elif isinstance(codeElement, ClassDefinition): for method in codeElement.methods: + ref_type = None if reference_name in method.raw: + if hasattr(method.signature, "type_hints") and reference_name in getattr(method.signature, "type_hints", []): + ref_type = "type_hint" + if cls._check_for_typehint_class_methods_attr_references( + imported_element=imported_element, + element_to_check=method, + ref_type=ref_type + ): + if matches_found >= matches_count: + break + continue method.references.append( CodeReference( unique_id=unique_id, - name=reference_name + name=reference_name, + type=ref_type ) ) matches_found += counts if matches_found >= matches_count: break for attribute in codeElement.attributes: + ref_type = None if reference_name in attribute.raw: + if reference_name == getattr(attribute, "type_hint", None): + ref_type = "type_hint" + if cls._check_for_typehint_class_methods_attr_references( + imported_element=imported_element, + element_to_check=attribute, + ref_type=ref_type + ): + if matches_found >= matches_count: + break + continue attribute.references.append( CodeReference( unique_id=unique_id, - name=reference_name + name=reference_name, + type=ref_type ) ) matches_found += counts @@ -546,7 +612,8 @@ def _find_references( codeElement.bases_references.append( CodeReference( unique_id=unique_id, - name=reference_name + name=reference_name, + type="inheritance" ) ) if matches_found > matches_count: From f5f55c52859f93f718473832544f50088112097c Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 15:57:03 +0100 Subject: [PATCH 47/88] feat: update intra-file dependency resolution to utilize CodeBase for improved structure --- tests/parsers/test_typescript_parser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/parsers/test_typescript_parser.py b/tests/parsers/test_typescript_parser.py index 8d21342..6c97c11 100644 --- a/tests/parsers/test_typescript_parser.py +++ b/tests/parsers/test_typescript_parser.py @@ -1,4 +1,4 @@ -from codetide.core.models import ImportStatement +from codetide.core.models import CodeBase, ImportStatement from codetide.parsers.typescript_parser import TypeScriptParser from tree_sitter import Parser @@ -174,7 +174,7 @@ class Helper { """ file_path = Path("test.ts") code_file = parser.parse_code(code.encode('utf-8'), file_path) - parser.resolve_intra_file_dependencies([code_file]) + parser.resolve_intra_file_dependencies(CodeBase(root=[code_file])) process_func = code_file.get("test.processData") assert process_func is not None assert any(ref.name == "Helper" for ref in process_func.references) From 3493a9332c8e1dfe2d448d6b4f55b210b027b19e Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 17:09:12 +0100 Subject: [PATCH 48/88] feat: add comprehensive tests for TypeScriptParser including edge cases and intra-file dependency resolution --- tests/parsers/test_typescript_parser.py | 84 +++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/tests/parsers/test_typescript_parser.py b/tests/parsers/test_typescript_parser.py index 6c97c11..860120b 100644 --- a/tests/parsers/test_typescript_parser.py +++ b/tests/parsers/test_typescript_parser.py @@ -43,6 +43,90 @@ def test_count_occurences_in_code(self, code, substring, count): """Tests the regex-based word occurrence counter.""" assert TypeScriptParser.count_occurences_in_code(code, substring) == count + def test_count_occurences_in_code_edge_cases(self): + """Test word boundary and substring edge cases.""" + assert TypeScriptParser.count_occurences_in_code("foofoobar foo", "foo") == 1 + assert TypeScriptParser.count_occurences_in_code("foo_bar foo", "foo") == 1 + assert TypeScriptParser.count_occurences_in_code("foo1 foo", "foo") == 1 + assert TypeScriptParser.count_occurences_in_code("foofoo foo", "foo") == 1 + assert TypeScriptParser.count_occurences_in_code("foo", "foo") == 1 + assert TypeScriptParser.count_occurences_in_code("bar", "foo") == 0 + + def test_variable_declaration_with_type_and_value(self, parser: TypeScriptParser): + code = "let x: number = 42;" + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + assert len(code_file.variables) == 1 + var = code_file.variables[0] + assert var.name == "x" + assert var.type_hint == ": number" + assert var.value == "42" + + def test_class_with_multiple_attributes_and_methods(self, parser: TypeScriptParser): + code = """ + class Multi { + public a: string = "A"; + public b: number = 2; + foo(): void {} + bar(): number { return 1; } + } + """ + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + assert len(code_file.classes) == 1 + cls = code_file.classes[0] + assert len(cls.attributes) == 2 + assert set(a.name for a in cls.attributes) == {"a", "b"} + assert len(cls.methods) == 2 + assert set(m.name for m in cls.methods) == {"foo", "bar"} + + def test_function_with_typehint_reference(self, parser: TypeScriptParser): + code = """ + class RefType {} + function useType(x: RefType): RefType { + return x; + } + """ + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + # Simulate codeBase for dependency resolution + class DummyCodeBase: + def __init__(self, root): + self.root = root + self._cached_elements = {} + for cf in root: + for c in cf.classes: + self._cached_elements[c.unique_id] = c + for f in cf.functions: + self._cached_elements[f.unique_id] = f + for v in cf.variables: + self._cached_elements[v.unique_id] = v + codeBase = DummyCodeBase([code_file]) + parser.resolve_intra_file_dependencies(codeBase, [code_file]) + func = code_file.get("test.useType") + assert func is not None + assert any(ref.type_hint == "RefType" for ref in func.signature.parameters) + + def test_class_inheritance_reference(self, parser: TypeScriptParser): + code = """ + class Base {} + class Derived extends Base {} + """ + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + class DummyCodeBase: + def __init__(self, root): + self.root = root + self._cached_elements = {} + for cf in root: + for c in cf.classes: + self._cached_elements[c.unique_id] = c + codeBase = DummyCodeBase([code_file]) + parser.resolve_intra_file_dependencies(codeBase, [code_file]) + derived = code_file.get("test.Derived") + assert derived is not None + assert any(ref == "Base" for ref in derived.bases) + def test_get_content_indentation(self, parser: TypeScriptParser): """Tests the _get_content method for preserving indentation.""" code = b"class MyClass {\n myMethod() {\n return 1;\n }\n}" From 7c3185c1425b69b72b5070033f63602db3797996 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 17:09:41 +0100 Subject: [PATCH 49/88] refactor: remove debug print statements from TestTypeScriptParser --- tests/parsers/test_typescript_parser.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/parsers/test_typescript_parser.py b/tests/parsers/test_typescript_parser.py index 860120b..42a8dea 100644 --- a/tests/parsers/test_typescript_parser.py +++ b/tests/parsers/test_typescript_parser.py @@ -131,7 +131,6 @@ def test_get_content_indentation(self, parser: TypeScriptParser): """Tests the _get_content method for preserving indentation.""" code = b"class MyClass {\n myMethod() {\n return 1;\n }\n}" codeFile = parser.parse_code(code, file_path="myMethod.ts") - print(f"{codeFile=}") assert "myMethod" in codeFile.raw assert codeFile.raw.startswith("class MyClass") @@ -142,7 +141,6 @@ async def test_parse_file(self, parser: TypeScriptParser, tmp_path: Path): code_content = "import { A } from 'mod';\nlet x = 10;" file_path.write_text(code_content, encoding="utf-8") code_file_model = await parser.parse_file(file_path) - print(f"{code_file_model=}") assert code_file_model.file_path == str(file_path.absolute()) assert len(code_file_model.imports) == 1 assert code_file_model.imports[0].source == "'mod'" From bbf18e8f5ba305f82ebc45f45c7356be33730882 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 17:12:51 +0100 Subject: [PATCH 50/88] feat: add replace_newline_in_quotes function to handle newlines in quoted strings --- codetide/agents/tide/prompts.py | 98 +++++++++++++---------- codetide/mcp/tools/patch_code/__init__.py | 32 +++++++- 2 files changed, 85 insertions(+), 45 deletions(-) diff --git a/codetide/agents/tide/prompts.py b/codetide/agents/tide/prompts.py index 5a0704d..ccf834a 100644 --- a/codetide/agents/tide/prompts.py +++ b/codetide/agents/tide/prompts.py @@ -109,8 +109,8 @@ """ WRITE_PATCH_SYSTEM_PROMPT = """ -You are Agent **Tide**, operating in **Patch Generation Mode** on **{DATE}**. -Your mission is to generate atomic, high-precision, diff-style patches that exactly satisfy the user’s request while adhering to the **STRICT PATCH PROTOCOL**. +You are Agent Tide, operating in Patch Generation Mode on {DATE}. +Your mission is to generate atomic, high-precision, diff-style patches that exactly satisfy the user’s request while adhering to the STRICT PATCH PROTOCOL. You are under zero-tolerance constraints: - No full-file rewrites @@ -122,7 +122,7 @@ --- -**RESPONSE FORMAT (ALWAYS):** +RESPONSE FORMAT (ALWAYS): ``` @@ -133,73 +133,87 @@ --- -**MANDATORY PATCH FORMAT:** +MANDATORY PATCH FORMAT (V4A-Compatible): - +```diff *** Begin Patch *** Update File: path/to/file.ext -@@ exact_code_block_header_from_getCodeContext (WITHOUT LINE NUMBERS) - +@@ context_block (function, class, etc. – no line numbers) + + + - line_to_remove + line_to_add - + + + *** End Patch - +``` --- -**PATCH STRUCTURE RULES:** +PATCH STRUCTURE RULES: + +* Use one \*\*\* \[ACTION] File: block per file -✅ For each file being modified, generate **only one patch block**. -If multiple edits are needed in different sections of a file, group them as **multiple `@@` hunks inside the same block**, all under one `*** Update File:` header. + * \[ACTION] must be one of Add, Update, or Delete -✅ Each `@@` header MUST: +* Inside each file patch: -* Contain only the **exact, unaltered line** from the target code block (function, class, control structure, or markdown section) -* Come directly from the coding context. -* Never include line numbers or placeholders like `@@ ---` + * Use one or more @@ context headers to uniquely identify the code location + * Include exactly 3 lines of context above and below the change + * If 3 lines are insufficient to uniquely locate the change, include one or more @@ lines to show nested context (e.g., class and method) -✅ Every line in the diff (context, removed, or added) MUST: +* Each @@ header MUST: -* Match the **original file byte-for-byte**, including spacing, punctuation, casing, formatting, and invisible characters -* Be sourced exactly from the original code via `getCodeContext()` or `getRepoTree(show_contents=True)` + * Contain the exact, unaltered line from the target code block (e.g., def func():, class MyClass:) + * Never include line numbers or placeholders like @@ --- -❌ DO NOT: +* Every line in the diff (context, removed, or added) MUST: -* Invent or paraphrase lines — all lines must exist exactly in the retrieved source -* Add or remove formatting, markdown symbols, or inferred structure -* Render or interpret markdown, HTML, JSON, or other syntax — preserve it exactly as-is -* Include edits outside the exact block of the `@@` header -* Use ellipses, abbreviations, or extra unchanged lines + * Match the original file byte-for-byte, including spacing, casing, indentation, punctuation, and invisible characters + * Be sourced exactly from getCodeContext() or getRepoTree(show\_contents=True) --- -**MARKDOWN-SPECIFIC RULES (e.g. README edits):** +DO NOT: -* When removing a bullet-point line that starts with `-`, prefix the diff line with `--` to remove it as literal content. -* **NEVER** interpret markdown or convert it to plaintext — preserve all syntax (e.g. `**bold**`, `#`, `[]()`, backticks) exactly as it appears. +* Invent, paraphrase, or transform lines — all lines must exist exactly in the source +* Add or remove formatting, inferred syntax, or markdown rendering +* Include edits outside the block scoped by the @@ header +* Use ellipses, placeholders, or extra unchanged lines -✅ Correct: +--- -``` --- - **Feature:** Add autosave -``` +SPECIAL CONTEXT RULES: -❌ Incorrect: +* If two changes occur close together, do not repeat overlapping context between them +* If the same block exists multiple times in a file, use multiple @@ headers (e.g., @@ class A, @@ def foo()) -``` -- Feature: Add autosave -``` +--- + +MARKDOWN-SPECIFIC RULES (e.g., README edits): + +* When removing a markdown bullet line starting with -, prefix the diff line with -- +* Never interpret markdown formatting (e.g., **bold**, headers, links) +* Preserve syntax literally + +Correct: +\-- - **Feature:** Add autosave + +Incorrect: + +* Feature: Add autosave --- -**FINAL CHECKLIST BEFORE PATCHING:** +FINAL CHECKLIST BEFORE PATCHING: -1. Validate that each line you edit exists exactly as-is in the block -2. Do not edit or reference lines outside the target block -3. Ensure one patch per file, with multiple `@@` hunks if needed -4. Do not interpret, render, or reformat content — preserve exact characters +1. Validate that every line you edit exists exactly as-is in the original context +2. Ensure one patch block per file, using multiple @@ hunks as needed +3. Include no formatting, layout, or interpretation changes +4. Match the structure of the apply\_patch tool’s expectations exactly This is a surgical, precision editing mode. -You must mirror source files exactly — no assumptions, no interpretation, no formatting changes. +You must mirror source files exactly — no assumptions, no reformatting, no transformations. """ \ No newline at end of file diff --git a/codetide/mcp/tools/patch_code/__init__.py b/codetide/mcp/tools/patch_code/__init__.py index 079aef7..b8eccf6 100644 --- a/codetide/mcp/tools/patch_code/__init__.py +++ b/codetide/mcp/tools/patch_code/__init__.py @@ -3,6 +3,9 @@ from typing import Dict, Tuple, List, Callable import pathlib +import re + +BREAKLINE_TOKEN = "" # --------------------------------------------------------------------------- # # User-facing API @@ -81,6 +84,29 @@ def apply_commit( if change.move_path and target_path != path: remove_fn(path) +def replace_newline_in_quotes(text, token=BREAKLINE_TOKEN): + pattern = r''' + (? str: with open(path, "rt", encoding="utf-8") as fh: - return fh.read() + return replace_newline_in_quotes(fh.read()) def write_file(path: str, content: str) -> None: target = pathlib.Path(path) target.parent.mkdir(parents=True, exist_ok=True) with target.open("wt", encoding="utf-8", newline="\n") as fh: - fh.write(content) + fh.write(content.replace(BREAKLINE_TOKEN, "\\n")) def remove_file(path: str) -> None: From fd8ee35894a9cba1da4637e0acb7d71f99898b0a Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 17:33:18 +0100 Subject: [PATCH 51/88] feat: add requirements-agents.txt and update setup.py to include agents dependency --- codetide/agents/tide/agent.py | 10 ++++++++-- requirements-agents.txt | 1 + setup.py | 4 +++- 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 requirements-agents.txt diff --git a/codetide/agents/tide/agent.py b/codetide/agents/tide/agent.py index 6053dbc..7ed9676 100644 --- a/codetide/agents/tide/agent.py +++ b/codetide/agents/tide/agent.py @@ -6,8 +6,14 @@ from .consts import AGENT_TIDE_ASCII_ART from .utils import parse_xml_content -from aicore.llm import Llm -from aicore.logger import _logger +try: + from aicore.llm import Llm + from aicore.logger import _logger +except ImportError as e: + raise ImportError( + "The 'codetide.agents' module requires the 'aicore' package. " + "Install it with: pip install codetide[agents]" + ) from e from prompt_toolkit.key_binding import KeyBindings from prompt_toolkit import PromptSession diff --git a/requirements-agents.txt b/requirements-agents.txt new file mode 100644 index 0000000..9c1c0d2 --- /dev/null +++ b/requirements-agents.txt @@ -0,0 +1 @@ +core-for-ai \ No newline at end of file diff --git a/setup.py b/setup.py index 8322a7e..7a06f6c 100644 --- a/setup.py +++ b/setup.py @@ -5,6 +5,7 @@ long_description = (here / "README.md").read_text(encoding="utf-8") requirements = (here / "requirements.txt").read_text(encoding="utf-8").splitlines() requirements_visualization = (here / "requirements-visualization.txt").read_text(encoding="utf-8").splitlines() +requirements_agents = (here / "requirements-agents.txt").read_text(encoding="utf-8").splitlines() setup( name="codetide", @@ -14,7 +15,8 @@ long_description_content_type="text/markdown", install_requires=requirements, extras_require={ - "visualization": requirements_visualization + "visualization": requirements_visualization, + "agents": requirements_agents }, entry_points={ "console_scripts": [ From 6f09f64b8bbcf054b61385af5b163ed529dec0df Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 19:31:44 +0100 Subject: [PATCH 52/88] feat: refactor parse_xml_content to parse_patch_blocks for improved patch handling --- codetide/agents/tide/utils.py | 24 +++++++++++------------ codetide/mcp/tools/patch_code/__init__.py | 18 ++++++++++++++++- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/codetide/agents/tide/utils.py b/codetide/agents/tide/utils.py index 0ac0a50..b7f5522 100644 --- a/codetide/agents/tide/utils.py +++ b/codetide/agents/tide/utils.py @@ -1,23 +1,23 @@ from typing import List, Union import re -def parse_xml_content(text: str, tag: str = "diff", multiple: bool = False) -> Union[str, List[str], None]: +def parse_patch_blocks(text: str, multiple: bool = True) -> Union[str, List[str], None]: """ - Extract content between ... markers. - + Extract content between *** Begin Patch and *** End Patch markers (inclusive), + ensuring that both markers are at zero indentation (start of line, no leading spaces). + Args: - text: Full input text. - tag: The tag name to extract content from (default: 'diff'). - multiple: If True, return all matching blocks. If False, return only the first one. - + text: Full input text containing one or more patch blocks. + multiple: If True, return a list of all patch blocks. If False, return the first match. + Returns: - The extracted content as a string (if one block), list of strings (if multiple), - or None if no blocks found. + A string (single patch), list of strings (multiple patches), or None if not found. """ - pattern = fr"<{tag}>\s*(.*?)\s*" - matches = re.findall(pattern, text, re.DOTALL) + + pattern = r"(?m)^(\*\*\* Begin Patch[\s\S]*?^\*\*\* End Patch)$" + matches = re.findall(pattern, text) if not matches: return None - return matches if multiple else matches[0] + return matches if multiple else matches[0] \ No newline at end of file diff --git a/codetide/mcp/tools/patch_code/__init__.py b/codetide/mcp/tools/patch_code/__init__.py index b8eccf6..b2f10a7 100644 --- a/codetide/mcp/tools/patch_code/__init__.py +++ b/codetide/mcp/tools/patch_code/__init__.py @@ -4,15 +4,30 @@ from typing import Dict, Tuple, List, Callable import pathlib import re +import os BREAKLINE_TOKEN = "" +BREAKLINE_PER_FILE_TYPE = { + ".md": "\n", + ".py": r"\n" +} + # --------------------------------------------------------------------------- # # User-facing API # --------------------------------------------------------------------------- # def text_to_patch(text: str, orig: Dict[str, str]) -> Tuple[Patch, int]: """High-level function to parse patch text against original file content.""" lines = text.splitlines() + for i, line in enumerate(lines): + if line.startswith(("@", "***")): + continue + + elif line.startswith("---") or not line.startswith(("+", "-", " ")): + lines[i] = f" {line}" + + # print(f"\n\n{lines[-2:]=}") + if not lines or not Parser._norm(lines[0]).startswith("*** Begin Patch"): raise DiffError("Invalid patch text - must start with '*** Begin Patch'.") if not Parser._norm(lines[-1]) == "*** End Patch": @@ -146,8 +161,9 @@ def open_file(path: str) -> str: def write_file(path: str, content: str) -> None: target = pathlib.Path(path) target.parent.mkdir(parents=True, exist_ok=True) + _, ext = os.path.splitext(target) with target.open("wt", encoding="utf-8", newline="\n") as fh: - fh.write(content.replace(BREAKLINE_TOKEN, "\\n")) + fh.write(content.replace(BREAKLINE_TOKEN, BREAKLINE_PER_FILE_TYPE.get(ext, r"\n"))) def remove_file(path: str) -> None: From fa701f939bc2492ad16e82176610968bfa17882c Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 19:32:29 +0100 Subject: [PATCH 53/88] refactor: enhance patch generation guidelines for clarity and precision --- codetide/agents/tide/prompts.py | 106 ++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 39 deletions(-) diff --git a/codetide/agents/tide/prompts.py b/codetide/agents/tide/prompts.py index ccf834a..0f99d3f 100644 --- a/codetide/agents/tide/prompts.py +++ b/codetide/agents/tide/prompts.py @@ -112,33 +112,31 @@ You are Agent Tide, operating in Patch Generation Mode on {DATE}. Your mission is to generate atomic, high-precision, diff-style patches that exactly satisfy the user’s request while adhering to the STRICT PATCH PROTOCOL. -You are under zero-tolerance constraints: -- No full-file rewrites -- No sloppy formatting -- No line numbers in patch headers -- No hallucinated context or edits -- No content interpretation or transformation -- Only minimal, valid, byte-accurate changes - --- RESPONSE FORMAT (ALWAYS): -``` - - + or , follow with a complete and valid patch block> -``` --- -MANDATORY PATCH FORMAT (V4A-Compatible): +### **MANDATORY PATCH FORMAT (V4A-Compatible):** + +Each patch must follow one of these structures, depending on the operation: `Update`, `Add`, or `Delete`. + +--- + +#### **Update Existing File** + +Use this when modifying content inside a file (including adding or changing lines in specific blocks): -```diff *** Begin Patch *** Update File: path/to/file.ext -@@ context_block (function, class, etc. – no line numbers) +@@ context_block_1 (function, class, etc. – no line numbers) +@@ context_block_2 (function, class, etc. – no line numbers) +@@ context_block_3 (function, class, etc. – no line numbers) @@ -148,7 +146,41 @@ *** End Patch -``` + +* You may include **multiple `@@` hunks** inside the same patch block if multiple changes are needed in that file. +* Always preserve context and formatting as returned by `getCodeContext()`. + +--- + +#### **Add New File** + +Use this when creating a completely new file: + +*** Begin Patch +*** Add File: path/to/new_file.ext ++ +*** End Patch + +* The file content must be complete, syntactically valid, and minimal. +* The lines must start with + to ensure propper diff formatting +* Only one `*** Add File:` block per new file. + +--- + +#### **Delete File** + +Use this when the user asks for a file to be removed: + +*** Begin Patch +*** Delete File: path/to/file_to_delete.ext +*** End Patch + +* Do **not** include any file contents in a delete block. + +--- + +**Do not mix Add, Delete, and Update directives in the same patch block.** +Each file operation must be fully self-contained and structurally valid. --- @@ -162,25 +194,34 @@ * Use one or more @@ context headers to uniquely identify the code location * Include exactly 3 lines of context above and below the change - * If 3 lines are insufficient to uniquely locate the change, include one or more @@ lines to show nested context (e.g., class and method) * Each @@ header MUST: - * Contain the exact, unaltered line from the target code block (e.g., def func():, class MyClass:) - * Never include line numbers or placeholders like @@ --- + * Contain a single, **unaltered, byte-exact line** from the original file that appears above the change + * This line MUST be present in the file verbatim, with exact casing, spacing, punctuation, and formatting + * Be the first exact context line above the diff, used literally + * Never be empty — DO NOT emit bare `@@` + * Never use synthetic placeholders like `@@ ---`, `@@`, or generated tags like `@@ section: intro` + +--- -* Every line in the diff (context, removed, or added) MUST: +PATCH CONTENT RULES: + +* Every line in the diff used for locattion (context, removed) MUST: * Match the original file byte-for-byte, including spacing, casing, indentation, punctuation, and invisible characters - * Be sourced exactly from getCodeContext() or getRepoTree(show\_contents=True) + * Start with @@ if it is an header or - if it is a line to removed + +* Every line in the diff that consist of new contents (addition) MUST: + * Start with + + * Contribute to achieve the user request according to the plain reasoning step you have previoulsy produced --- DO NOT: -* Invent, paraphrase, or transform lines — all lines must exist exactly in the source +* Invent, paraphrase, or transform location lines — all lines must exist exactly in the source * Add or remove formatting, inferred syntax, or markdown rendering -* Include edits outside the block scoped by the @@ header * Use ellipses, placeholders, or extra unchanged lines --- @@ -192,27 +233,14 @@ --- -MARKDOWN-SPECIFIC RULES (e.g., README edits): - -* When removing a markdown bullet line starting with -, prefix the diff line with -- -* Never interpret markdown formatting (e.g., **bold**, headers, links) -* Preserve syntax literally - -Correct: -\-- - **Feature:** Add autosave - -Incorrect: - -* Feature: Add autosave - ---- - FINAL CHECKLIST BEFORE PATCHING: 1. Validate that every line you edit exists exactly as-is in the original context 2. Ensure one patch block per file, using multiple @@ hunks as needed 3. Include no formatting, layout, or interpretation changes -4. Match the structure of the apply\_patch tool’s expectations exactly +4. Ensure every @@ header is a valid, real, byte-identical line from the original file +5. Match the structure of the apply\_patch tool’s expectations exactly +6. Ensure each patch line starts with a `@`, `+`, `-` or ` ` This is a surgical, precision editing mode. You must mirror source files exactly — no assumptions, no reformatting, no transformations. From 263d3723bd9547c78ffe4e22053fb1f94afb9b36 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 19:32:45 +0100 Subject: [PATCH 54/88] refactor: replace parse_xml_content with parse_patch_blocks for improved patch processing --- codetide/agents/tide/agent.py | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/codetide/agents/tide/agent.py b/codetide/agents/tide/agent.py index 7ed9676..5c88713 100644 --- a/codetide/agents/tide/agent.py +++ b/codetide/agents/tide/agent.py @@ -4,7 +4,7 @@ AGENT_TIDE_SYSTEM_PROMPT, GET_CODE_IDENTIFIERS_SYSTEM_PROMPT, WRITE_PATCH_SYSTEM_PROMPT ) from .consts import AGENT_TIDE_ASCII_ART -from .utils import parse_xml_content +from .utils import parse_patch_blocks try: from aicore.llm import Llm @@ -20,7 +20,6 @@ from pydantic import BaseModel from typing import Optional from datetime import date -from tqdm import tqdm import asyncio import os @@ -66,24 +65,15 @@ async def agent_loop(self): prefix_prompt=codeContext ) - diffPatches = parse_xml_content(response, multiple=True) + diffPatches = parse_patch_blocks(response, multiple=True) if diffPatches: - diffPatches = [diffPatch.replace("\'", "'") for diffPatch in diffPatches] - diffPatches = [diffPatch.replace('\"', '"') for diffPatch in diffPatches] - for diffPatch in tqdm(diffPatches): - process_patch(diffPatch, open_file, write_file, remove_file, file_exists) + + for patch in diffPatches: + patch = patch.replace("\'", "'").replace('\"', '"') + process_patch(patch, open_file, write_file, remove_file, file_exists) + await self.tide.check_for_updates(serialize=True, include_cached_ids=True) - - # else: - # response = await self.llm.acomplete( - # self.history, - # system_prompt=[ - # AGENT_TIDE_SYSTEM_PROMPT.format(DATE=TODAY), - # ASSISTANT_SYSTEM_PROMPT.format(DATE=TODAY) - # ], - # prefix_prompt=repo_tree - # ) self.history.append(response) From dee4bb4cebd0dd7a9348f9e8a61144aa7256952e Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 19:32:58 +0100 Subject: [PATCH 55/88] test: add unit tests for parse_patch_blocks function --- tests/mcp/tools/test_apply_patch.py | 11 --- .../tools/test_utils_parse_patch_blocks.py | 71 +++++++++++++++++++ 2 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 tests/mcp/tools/test_utils_parse_patch_blocks.py diff --git a/tests/mcp/tools/test_apply_patch.py b/tests/mcp/tools/test_apply_patch.py index e420a67..613cec0 100644 --- a/tests/mcp/tools/test_apply_patch.py +++ b/tests/mcp/tools/test_apply_patch.py @@ -430,15 +430,4 @@ def test_error_invalid_line_in_add(mock_fs): *** End Patch """ with pytest.raises(DiffError, match=r"Unknown or malformed action line"): - mock_fs.apply_patch(patch) - - -def test_error_invalid_line_in_update(mock_fs): - patch = """*** Begin Patch -*** Update File: main.py -@@ def hello(): -this line is invalid -*** End Patch -""" - with pytest.raises(DiffError, match=r"must start with '\+', '-', or ' '"): mock_fs.apply_patch(patch) \ No newline at end of file diff --git a/tests/mcp/tools/test_utils_parse_patch_blocks.py b/tests/mcp/tools/test_utils_parse_patch_blocks.py new file mode 100644 index 0000000..d9308f0 --- /dev/null +++ b/tests/mcp/tools/test_utils_parse_patch_blocks.py @@ -0,0 +1,71 @@ +from codetide.agents.tide.utils import parse_patch_blocks + +def test_single_patch_block(): + text = ( + "Some intro\n" + "*** Begin Patch\n" + "patch content 1\n" + "*** End Patch\n" + "Some outro" + ) + result = parse_patch_blocks(text, multiple=False) + assert result == "*** Begin Patch\npatch content 1\n*** End Patch" + +def test_multiple_patch_blocks(): + text = ( + "*** Begin Patch\n" + "patch content 1\n" + "*** End Patch\n" + "irrelevant\n" + "*** Begin Patch\n" + "patch content 2\n" + "*** End Patch\n" + ) + result = parse_patch_blocks(text, multiple=True) + assert isinstance(result, list) + assert len(result) == 2 + assert result[0] == "*** Begin Patch\npatch content 1\n*** End Patch" + assert result[1] == "*** Begin Patch\npatch content 2\n*** End Patch" + +def test_no_patch_block_returns_none(): + text = "No patch markers here" + assert parse_patch_blocks(text, multiple=True) is None + assert parse_patch_blocks(text, multiple=False) is None + +def test_patch_block_with_indentation_is_ignored(): + text = ( + " *** Begin Patch\n" + "indented patch\n" + "*** End Patch\n" + "*** Begin Patch\n" + "valid patch\n" + "*** End Patch\n" + ) + result = parse_patch_blocks(text, multiple=True) + assert len(result) == 1 + assert result[0] == "*** Begin Patch\nvalid patch\n*** End Patch" + +def test_patch_block_at_start_and_end(): + text = ( + "*** Begin Patch\n" + "start patch\n" + "*** End Patch\n" + "middle\n" + "*** Begin Patch\n" + "end patch\n" + "*** End Patch" + ) + result = parse_patch_blocks(text, multiple=True) + assert len(result) == 2 + assert result[0] == "*** Begin Patch\nstart patch\n*** End Patch" + assert result[1] == "*** Begin Patch\nend patch\n*** End Patch" + +def test_patch_block_with_extra_content_inside(): + text = ( + "*** Begin Patch\n" + "line1\n" + "line2\n" + "*** End Patch\n" + ) + result = parse_patch_blocks(text, multiple=False) + assert result == "*** Begin Patch\nline1\nline2\n*** End Patch" From 24c3ccfbf4c0c9f973e6814e870de1a88b7a5f08 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 19:35:53 +0100 Subject: [PATCH 56/88] feat: introduce AgentTide module for enhanced code context and patch generation --- README.md | 54 +++++++++++++++++++++++-- examples/agent_tide.py | 92 ++++++++---------------------------------- 2 files changed, 67 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index b96679e..230a69a 100644 --- a/README.md +++ b/README.md @@ -446,9 +446,57 @@ Here’s what’s next for CodeTide: ~~- 🧭 **Handle relative imports** in Python projects → Improve resolution for intra-package navigation.~~ -- 🤖 **Long-term vision**: Release a native **CodeTide Agent** - → Seamless, intelligent context resolution directly integrated into the CodeTide core. - → Unlock **clinical issue detection**, **guided refactors**, and **agent-level navigation**. + +--- + +## 🤖 Agents Module: AgentTide + +CodeTide now includes an `agents` module, featuring **AgentTide**—a precision-driven software engineering agent that connects directly to your codebase and executes your requests with full code context. + +**AgentTide** leverages CodeTide’s symbolic code understanding to: +- Retrieve and reason about relevant code context for any request +- Generate atomic, high-precision patches using strict protocols +- Apply changes directly to your codebase, with robust validation + +### Where to Find It +- Source: [`codetide/agents/tide/agent.py`](codetide/agents/tide/agent.py) + +### What It Does +AgentTide acts as an autonomous agent that: +- Connects to your codebase using CodeTide’s parsing and context tools +- Interacts with users via a conversational interface +- Identifies relevant files, classes, and functions for any request +- Generates and applies diff-style patches, ensuring code quality and requirements fidelity + +### Example Usage +To use AgentTide, ensure you have the `aicore` package installed (`pip install codetide[agents]`), then instantiate and run the agent: + +```python +from codetide import CodeTide +from codetide.agents.tide.agent import AgentTide +from aicore.llm import Llm, LlmConfig +import os, asyncio + +async def main(): + tide = await CodeTide.from_path("/path/to/your/repo") + llm = Llm.from_config( + LlmConfig( + model="deepseek-chat", + provider="deepseek", + temperature=0, + api_key=os.getenv("DEEPSEEK-API-KEY") + ) + ) + agent = AgentTide(llm=llm, tide=tide) + await agent.run() + +if __name__ == "__main__": + asyncio.run(main()) +``` + +AgentTide will prompt you for requests, retrieve the relevant code context, and generate precise patches to fulfill your requirements. + +For more details, see the [agents module source code](codetide/agents/tide/agent.py). --- diff --git a/examples/agent_tide.py b/examples/agent_tide.py index d274965..38f864e 100644 --- a/examples/agent_tide.py +++ b/examples/agent_tide.py @@ -1,96 +1,36 @@ """ -install aicore - pip install core-for-ai -Make sure to set `CODETIDE_WORKSPACE` env var which will be requried for the MCP server to work +Install the agents version with pip install codetide[agents] """ -from codetide.mcp.agent_tide_system import AGENT_TIDE_SYSTEM_PROMPT -from codetide.mcp import codeTideMCPServer +from codetide.mcp.utils import initCodeTide +from codetide.agents.tide import AgentTide from aicore.llm import Llm, LlmConfig -from aicore.logger import _logger - -from prompt_toolkit.key_binding import KeyBindings -from prompt_toolkit import PromptSession -from typing import Optional import os -AGENT_TIDE_ASCII_ART = """ - -█████╗ ██████╗ ███████╗███╗ ██╗████████╗ ████████╗██╗██████╗ ███████╗ -██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝ ╚══██╔══╝██║██╔══██╗██╔════╝ -███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ██║ ██║██║ ██║█████╗ -██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ██║ ██║██║ ██║██╔══╝ -██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ██║ ██║██████╔╝███████╗ -╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝ - -""" - def init_llm()->Llm: llm = Llm.from_config( LlmConfig( - model="deepseek-chat", - provider="deepseek", + # model="deepseek-chat", + # provider="deepseek", + # api_key=os.getenv("DEEPSEEK-API-KEY") + model="gpt-4.1", + provider="openai", + api_key=os.getenv("OPENAI-API-KEY"), temperature=0, - api_key=os.getenv("DEEPSEEK-API-KEY") ) ) - - llm.provider.mcp.add_server(name=codeTideMCPServer.name, parameters=codeTideMCPServer) return llm -def trim_messages(messages, tokenizer_fn, max_tokens :Optional[int]=None): - max_tokens = max_tokens or int(os.environ.get("MAX_HISTORY_TOKENS", 1028)) - while messages and sum(len(tokenizer_fn(msg)) for msg in messages) > max_tokens: - messages.pop(0) # Remove from the beginning - -async def main(max_tokens: int = 48000): +async def main(): llm = init_llm() - history = [] - - # 1. Set up key bindings - bindings = KeyBindings() - - @bindings.add('escape') - def _(event): - """When Esc is pressed, exit the application.""" - _logger.logger.warning("Escape key pressed — exiting...") - event.app.exit() + tide = await initCodeTide() - # 2. Create a prompt session with the custom key bindings - session = PromptSession(key_bindings=bindings) + tide = AgentTide(llm=llm, tide=tide) + await tide.run() - _logger.logger.info(f"\n{AGENT_TIDE_ASCII_ART}\nReady to surf. Press ESC to exit.\n") - try: - while True: - try: - # 3. Use the async prompt instead of input() - message = await session.prompt_async("You: ") - message = message.strip() - - if not message: - continue - - except (EOFError, KeyboardInterrupt): - # prompt_toolkit raises EOFError on Ctrl-D and KeyboardInterrupt on Ctrl-C - _logger.warning("\nExiting...") - break - - history.append(message) - trim_messages(history, llm.tokenizer, max_tokens) - - print("Agent: Thinking...") - response = await llm.acomplete(history, system_prompt=[AGENT_TIDE_SYSTEM_PROMPT], as_message_records=True) - print(f"Agent: {response}") - history.extend(response) - - except asyncio.CancelledError: - # This can happen if the event loop is shut down - pass - finally: - _logger.logger.info("\nExited by user. Goodbye!") - -if __name__ == "__main__": +if __name__ == "__main__": from dotenv import load_dotenv import asyncio - + load_dotenv() - asyncio.run(main()) + asyncio.run(main()) \ No newline at end of file From 87e418f649a3a151aac41a0833838a01a39d3215 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Fri, 18 Jul 2025 19:43:25 +0100 Subject: [PATCH 57/88] test: add comprehensive unit tests for parse_patch_blocks function --- tests/{mcp/tools => agents/tide}/test_utils_parse_patch_blocks.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{mcp/tools => agents/tide}/test_utils_parse_patch_blocks.py (100%) diff --git a/tests/mcp/tools/test_utils_parse_patch_blocks.py b/tests/agents/tide/test_utils_parse_patch_blocks.py similarity index 100% rename from tests/mcp/tools/test_utils_parse_patch_blocks.py rename to tests/agents/tide/test_utils_parse_patch_blocks.py From 32a8e115a6853d1bb37d38e091c0f5f8980b12bc Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 00:33:25 +0100 Subject: [PATCH 58/88] feat: add agent testing to CI workflow and configure pytest to ignore agents tests --- .github/workflows/python_package.yml | 5 +++++ pytest.ini | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 pytest.ini diff --git a/.github/workflows/python_package.yml b/.github/workflows/python_package.yml index c1775d5..156ff65 100644 --- a/.github/workflows/python_package.yml +++ b/.github/workflows/python_package.yml @@ -45,6 +45,11 @@ jobs: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: pytest tests/ + - name: Install package with agents requirements + run: | + python -m pip install .[agents] + - name: Test agents with pytest + run: pytest tests/agents security: name: Security Check runs-on: ubuntu-latest diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..6be6618 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[tool:pytest] +addopts = --ignore=test/agents \ No newline at end of file From 5620a1026c08100be9a3a6e72a082ee529fc63f1 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 00:35:22 +0100 Subject: [PATCH 59/88] fix: ensure newline at end of requirements.txt for proper formatting --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fa12af6..711d4ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ pyyaml==6.0.2 tree-sitter==0.24.0 tree-sitter-python==0.23.6 tree-sitter-typescript==0.23.2 -fastmcp==2.9.2 \ No newline at end of file +fastmcp==2.9.2 +ulid==1.1 \ No newline at end of file From f03c8c405bf701f6db48ba9f1b1b4c05ea718d95 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 00:36:35 +0100 Subject: [PATCH 60/88] fix: update core-for-ai version in requirements-agents.txt to >=0.1.97 --- requirements-agents.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-agents.txt b/requirements-agents.txt index 9c1c0d2..329940a 100644 --- a/requirements-agents.txt +++ b/requirements-agents.txt @@ -1 +1 @@ -core-for-ai \ No newline at end of file +core-for-ai>=0.1.97 \ No newline at end of file From 102c3c08f0a0e63b7d2abbab0beee483b7fb8afd Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 00:38:36 +0100 Subject: [PATCH 61/88] fix: update setup.py to include author details and project URL --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7a06f6c..b8dad56 100644 --- a/setup.py +++ b/setup.py @@ -10,12 +10,16 @@ setup( name="codetide", version="0.0.21", + author="Bruno V.", + author_email="bruno.vitorino@tecnico.ulisboa.pt", + description="CodeTide is a fully local, privacy-preserving tool for parsing and understanding Python codebases using symbolic, structural analysis. No internet, no LLMs, no embeddings - just fast, explainable, and deterministic code intelligence.", packages=find_packages(), long_description=long_description, long_description_content_type="text/markdown", + url="https://github.com/BrunoV21/CodeTide", install_requires=requirements, extras_require={ - "visualization": requirements_visualization, + "visualization": requirements_visualization, "agents": requirements_agents }, entry_points={ From 8375a7c398cc0e82078aea20c0765e9f77c5c88e Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 00:48:52 +0100 Subject: [PATCH 62/88] fix: update pytest command to ignore agents tests and remove pytest.ini configuration --- .github/workflows/python_package.yml | 2 +- pytest.ini | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 pytest.ini diff --git a/.github/workflows/python_package.yml b/.github/workflows/python_package.yml index 156ff65..e606c29 100644 --- a/.github/workflows/python_package.yml +++ b/.github/workflows/python_package.yml @@ -44,7 +44,7 @@ jobs: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest - run: pytest tests/ + run: pytest tests --ignore=tests/agents - name: Install package with agents requirements run: | python -m pip install .[agents] diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 6be6618..0000000 --- a/pytest.ini +++ /dev/null @@ -1,2 +0,0 @@ -[tool:pytest] -addopts = --ignore=test/agents \ No newline at end of file From 0eee8109d0cf2d68ac908f07ac08840137ba13a1 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 10:17:25 +0100 Subject: [PATCH 63/88] fix: enhance docstring detection to ensure minimum length --- codetide/parsers/python_parser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codetide/parsers/python_parser.py b/codetide/parsers/python_parser.py index f08c212..4e62ebb 100644 --- a/codetide/parsers/python_parser.py +++ b/codetide/parsers/python_parser.py @@ -46,9 +46,9 @@ def is_docstring(content :str)->bool: return False stripped = content.strip() - if stripped.startswith('"""') and stripped.endswith('"""'): + if stripped.startswith('"""') and stripped.endswith('"""') and len(stripped) > 3: return True - elif stripped.startswith("'''") and stripped.endswith("'''"): + elif stripped.startswith("'''") and stripped.endswith("'''") and len(stripped) > 3: return True return False From 4c8d3ddaf0daa3ab46190b08fe46d40048c0dc38 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 10:17:38 +0100 Subject: [PATCH 64/88] test: add comprehensive tests for docstring detection and parsing --- tests/parsers/test_python_parser.py | 140 ++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/tests/parsers/test_python_parser.py b/tests/parsers/test_python_parser.py index 2c2adc0..e94a882 100644 --- a/tests/parsers/test_python_parser.py +++ b/tests/parsers/test_python_parser.py @@ -14,6 +14,146 @@ def parser() -> PythonParser: class TestPythonParser: + @pytest.mark.parametrize("content,expected", [ + ('"""This is a docstring"""', True), + ("'''This is a docstring'''", True), + ('"""Multiline\ndocstring"""', True), + ("'''Multiline\ndocstring'''", True), + ('"Not a docstring"', False), + ("'Not a docstring'", False), + ("", False), + (None, False), + ('"""Unclosed docstring', False), + ("'''Unclosed docstring", False), + ('""" """', True), + ("''' '''", True), + ('"""', False), + ("'''", False), + ]) + def test_is_docstring(self, content, expected): + assert PythonParser.is_docstring(content) == expected + + @pytest.mark.parametrize("raw,docstring,expected", [ + ("def f():\n pass", None, None), + ("def f():\n \"\"\"Docstring\"\"\"", '"""Docstring"""', "def f():\n \"\"\"Docstring\"\"\""), + ("def f():\n '''Docstring'''", "'''Docstring'''", "def f():\n '''Docstring'''"), + ("def f():\n pass", "", None), + ("def f():\n not a docstring", "not a docstring", "def f():\n not a docstring"), + ("def f():\n \"\"\"Multi\nLine\"\"\"", '"""Multi\nLine"""', "def f():\n \"\"\"Multi\nLine\"\"\""), + ]) + def test_compile_docstring(self, raw, docstring, expected): + assert PythonParser.compile_docstring(raw, docstring) == expected + + @pytest.mark.parametrize("code, expected_docstring", [ + ( + ''' +def foo(): + """This is a function docstring.""" + return 1 +''', + '"""This is a function docstring."""' + ), + ( + """ +def bar(): + ''' + Multiline + docstring + ''' + return 2 +""", + "'''\n Multiline\n docstring\n '''" + ), + ( + ''' +def no_doc(): + return 3 +''', + None + ), + ( + ''' +def empty_doc(): + """ """ + return 4 +''', + '""" """' + ), + ]) + def test_parse_function_docstring(self, parser: PythonParser, code, expected_docstring): + file_path = Path("test.py") + code_file = parser.parse_code(code.encode("utf-8"), file_path) + func = code_file.functions[0] + if expected_docstring is None: + assert func.docstring is None + else: + assert expected_docstring in func.docstring + + @pytest.mark.parametrize("code, expected_docstring", [ + ( + ''' +class Foo: + """Class docstring.""" + x = 1 +''', + '"""Class docstring."""' + ), + ( + """ +class Bar: + ''' + Multiline + class docstring + ''' + y = 2 +""", + "'''\n Multiline\n class docstring\n '''" + ), + ( + ''' +class NoDoc: + z = 3 +''', + None + ), + ( + ''' +class EmptyDoc: + """ """ + a = 4 +''', + '""" """' + ), + ]) + def test_parse_class_docstring(self, parser: PythonParser, code, expected_docstring): + file_path = Path("test.py") + code_file = parser.parse_code(code.encode("utf-8"), file_path) + cls = code_file.classes[0] + if expected_docstring is None: + assert cls.docstring is None + else: + assert expected_docstring in cls.docstring + + @pytest.mark.parametrize("import_stmt, expected", [ + (ImportStatement(source="os"), "import os"), + (ImportStatement(name="numpy", alias="np"), "import numpy as np"), + (ImportStatement(source="pathlib", name="Path"), "from pathlib import Path"), + (ImportStatement(source="collections", name="deque", alias="dq"), "from collections import deque as dq"), + (ImportStatement(source="typing"), "import typing"), + ]) + def test_import_statement_template(self, import_stmt, expected): + assert PythonParser.import_statement_template(import_stmt) == expected + + def test_relative_import_parsing(self, parser: PythonParser): + code = "from .submodule import foo" + file_path = Path("pkg/__init__.py") + code_file = parser.parse_code(code.encode("utf-8"), file_path) + assert len(code_file.imports) == 1 + imp = code_file.imports[0] + assert imp.import_type == "relative" + assert imp.source is not None + assert "submodule" in imp.source + def test_initialization(self, parser: PythonParser): """Tests the basic properties and initialization of the parser.""" assert parser.language == "python" From 99ccf7493362b7390555d5f2fd938fe52dcd6c48 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 11:10:39 +0100 Subject: [PATCH 65/88] feat: add support for processing export statements and enhance function definition handling in TypeScriptParser --- codetide/parsers/typescript_parser.py | 43 +++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/codetide/parsers/typescript_parser.py b/codetide/parsers/typescript_parser.py index 7474541..d63cbd8 100644 --- a/codetide/parsers/typescript_parser.py +++ b/codetide/parsers/typescript_parser.py @@ -99,12 +99,41 @@ async def parse_file(self, file_path: Union[str, Path], root_path: Optional[Unio file_path = file_path.relative_to(Path(root_path)) codeFile = await loop.run_in_executor(pool, self.parse_code, code, file_path) return codeFile + + @staticmethod + def _is_type(node: Node, child_type :str)->bool: + for child in node.children: + if child.type == child_type: + return True + return False + + @classmethod + def _process_export_statement(cls, node: Node, code: bytes, codeFile: CodeFileModel)->Tuple[List[str], List[str]]: + decorators = [] + modifiers = [] + for child in node.children: + if child.type == "decorator": + decorators.append(cls._get_content(code, child)) + + elif child.type == "export": + modifiers.append(cls._get_content(code, child)) + + elif child.type == "function_declaration": + cls._process_function_definition(child, code, codeFile, modifiers=modifiers, decorators=decorators) + + elif child.type == "class_declaration": + cls._process_class_node(child, code, codeFile) + + elif child.type == "expression_statement": + cls._process_expression_statement(child, code, codeFile) @classmethod def _process_node(cls, node: Node, code: bytes, codeFile: CodeFileModel): for child in node.children: if child.type == "import_statement": cls._process_import_node(child, code, codeFile) + elif child.type == "export_statement": + cls._process_export_statement(child, code, codeFile) elif child.type == "class_declaration": cls._process_class_node(child, code, codeFile) elif child.type == "function_declaration": @@ -177,10 +206,11 @@ def _process_import_node(cls, node: Node, code: bytes, codeFile: CodeFileModel): ) codeFile.add_import(importStatement) - cls._generate_unique_import_id(codeFile.imports[-1]) + cls._generate_unique_import_id(codeFile.imports[-1]) @classmethod def _process_class_node(cls, node: Node, code: bytes, codeFile: CodeFileModel): + # TODO add support for modifiers at variables, classes class_name = None bases = [] raw = cls._get_content(code, node) @@ -243,11 +273,13 @@ def _process_class_attribute(cls, node: Node, code: bytes, codeFile: CodeFileMod )) @classmethod - def _process_function_definition(cls, node: Node, code: bytes, codeFile: CodeFileModel, is_method :bool=False): + def _process_function_definition(cls, node: Node, code: bytes, codeFile: CodeFileModel, is_method :bool=False, modifiers :Optional[List[str]]=None, decorators :Optional[List[str]]=None): definition = None signature = FunctionSignature() - modifiers = [] - decorators = [] + if modifiers is None: + modifiers = [] + if decorators is None: + decorators = [] raw = cls._get_content(code, node) for child in node.children: if child.type == "identifier" and definition is None: @@ -270,8 +302,7 @@ def _process_function_definition(cls, node: Node, code: bytes, codeFile: CodeFil modifiers.append("protected") elif child.type == "static": modifiers.append("static") - elif child.type == "async": - modifiers.append("async") + if not is_method: codeFile.add_function(FunctionDefinition( name=definition, From 772a6f4815d95375d55905647359155650015e78 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 11:11:11 +0100 Subject: [PATCH 66/88] refactor: comment out unused server imports and enhance code context validation logic --- codetide/mcp/tools/apply_patch.py | 18 ++++-------------- codetide/mcp/tools/check_code_identifiers.py | 5 +++-- codetide/mcp/tools/get_code_context.py | 15 ++++++++++++++- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/codetide/mcp/tools/apply_patch.py b/codetide/mcp/tools/apply_patch.py index 9ea13e1..8ba72f5 100644 --- a/codetide/mcp/tools/apply_patch.py +++ b/codetide/mcp/tools/apply_patch.py @@ -1,11 +1,12 @@ from .patch_code import DiffError, file_exists, open_file, process_patch, remove_file, write_file -from ..server import codeTideMCPServer +# from ..server import codeTideMCPServer from ...core.common import writeFile from ..utils import initCodeTide from ulid import ulid -@codeTideMCPServer.tool +# @codeTideMCPServer.tool +# TODO needs to be more stable before reintroducing it async def applyPatch(patch_text: str) -> str: """ Call this tool with a diff-like text to make file changes. Whenever you need to write code, this is the tool for you. @@ -192,15 +193,4 @@ def run(): if "exc" in locals(): writeFile(patch_text, patch_path) - return result - -if __name__ == "__main__": - from codetide.core.common import writeFile, readFile - from codetide.mcp.tools.patch_code import DiffError, file_exists, open_file, process_patch, remove_file, write_file - from codetide.mcp.server import codeTideMCPServer - from codetide.mcp.utils import initCodeTide - - import asyncio - patch_path = "./storage/01JZ9969DN4A8C98HH2CXVXAFF.txt" - patch = readFile(patch_path) - asyncio.run(applyPatch(patch)) \ No newline at end of file + return result \ No newline at end of file diff --git a/codetide/mcp/tools/check_code_identifiers.py b/codetide/mcp/tools/check_code_identifiers.py index 0fa7e73..b77681b 100644 --- a/codetide/mcp/tools/check_code_identifiers.py +++ b/codetide/mcp/tools/check_code_identifiers.py @@ -1,11 +1,12 @@ from ...autocomplete import AutoComplete from ..utils import initCodeTide -from ..server import codeTideMCPServer +# from ..server import codeTideMCPServer from typing import List import orjson -@codeTideMCPServer.tool +# @codeTideMCPServer.tool +# incorporated into get_code_context tool async def checkCodeIdentifiers(code_identifiers: List[str]) -> str: """ Validates code identifiers against cached repository entries and suggests corrections. diff --git a/codetide/mcp/tools/get_code_context.py b/codetide/mcp/tools/get_code_context.py index c9d6366..046f392 100644 --- a/codetide/mcp/tools/get_code_context.py +++ b/codetide/mcp/tools/get_code_context.py @@ -1,3 +1,4 @@ +from ...autocomplete import AutoComplete from ..server import codeTideMCPServer from ..utils import initCodeTide @@ -45,5 +46,17 @@ async def getCodeContext( """ tide = await initCodeTide() - context = tide.get(code_identifiers, context_depth, as_string=True) + if code_identifiers: + autocomplete = AutoComplete(tide.cached_ids) + # Validate each code identifier + validated_code_indentifiers = [] + for code_id in code_identifiers: + result = autocomplete.validate_code_identifier(code_id) + if result.get("is_valid"): + validated_code_indentifiers.append(code_id) + + elif result.get("matching_identifiers"): + validated_code_indentifiers.append(result.get("matching_identifiers")[0]) + + context = tide.get(validated_code_indentifiers, context_depth, as_string=True) return context if context else f"{code_identifiers} are not valid code_identifiers, refer to the getRepoTree tool to get a sense of the correct identifiers" \ No newline at end of file From 9c7f69d02db380e6b49e558c7fa1f54a8fb617e1 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 11:33:39 +0100 Subject: [PATCH 67/88] feat: enhance import processing to support multiple names and aliases in TypeScriptParser --- codetide/parsers/typescript_parser.py | 77 ++++++++++++++++----------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/codetide/parsers/typescript_parser.py b/codetide/parsers/typescript_parser.py index d63cbd8..f2cf7ba 100644 --- a/codetide/parsers/typescript_parser.py +++ b/codetide/parsers/typescript_parser.py @@ -144,47 +144,52 @@ def _process_node(cls, node: Node, code: bytes, codeFile: CodeFileModel): cls._process_expression_statement(child, code, codeFile) @classmethod - def _process_import_clause_node(cls, node: Node, code: bytes)->Tuple[Optional[str], Optional[str]]: - alias = None - next_is_alias = False + def _process_import_clause_node(cls, node: Node, code: bytes) -> Tuple[List[str], List[Optional[str]]]: + names = [] + aliases = [] + for child in node.children: if child.type == "named_imports": for import_child in child.children: if import_child.type == "import_specifier": + current_name = None + current_alias = None + next_is_alias = False + for alias_child in import_child.children: if alias_child.type == "identifier" and not next_is_alias: - name = cls._get_content(code, alias_child) + current_name = cls._get_content(code, alias_child) elif alias_child.type == "as": next_is_alias = True elif alias_child.type == "identifier" and next_is_alias: - alias = cls._get_content(code, alias_child) + current_alias = cls._get_content(code, alias_child) next_is_alias = False - - if name and alias: - return name, alias - - name = cls._get_content(code, import_child) - return name, alias - + + if current_name: + names.append(current_name) + aliases.append(current_alias) + elif child.type == "identifier": name = cls._get_content(code, child) - return name, alias - - return None, None + if name: + names.append(name) + aliases.append(None) + + return names, aliases @classmethod def _process_import_node(cls, node: Node, code: bytes, codeFile: CodeFileModel): source = None - name = None - alias = None - # is_relative = False + names = [] + aliases = [] next_is_from_import = False next_is_import = False + for child in node.children: if child.type == "import": next_is_import = True elif child.type == "import_clause" and next_is_import: - name, alias = cls._process_import_clause_node(child, code) + names, aliases = cls._process_import_clause_node(child, code) next_is_import = False elif next_is_import: source = cls._get_content(code, child) @@ -193,20 +198,30 @@ def _process_import_node(cls, node: Node, code: bytes, codeFile: CodeFileModel): next_is_from_import = True elif child.type == "string" and next_is_from_import: source = cls._get_content(code, child) - - if name and source is None: - source = name - name = None + if names and source is None: + source = names[0] if len(names) == 1 else None + if source: + names = [] + aliases = [] if source: - importStatement = ImportStatement( - source=source, - name=name, - alias=alias - ) - - codeFile.add_import(importStatement) - cls._generate_unique_import_id(codeFile.imports[-1]) + if names: + for name, alias in zip(names, aliases): + importStatement = ImportStatement( + source=source, + name=name, + alias=alias + ) + codeFile.add_import(importStatement) + cls._generate_unique_import_id(codeFile.imports[-1]) + else: + importStatement = ImportStatement( + source=source, + name=None, + alias=None + ) + codeFile.add_import(importStatement) + cls._generate_unique_import_id(codeFile.imports[-1]) @classmethod def _process_class_node(cls, node: Node, code: bytes, codeFile: CodeFileModel): From cb5bb02b41f0565d73c5997ca91b06a8abf15442 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 11:33:59 +0100 Subject: [PATCH 68/88] feat: enhance code identifier validation and improve prompt instructions in AgentTide --- codetide/agents/tide/agent.py | 21 +++++++++++++++------ codetide/agents/tide/prompts.py | 11 +++++------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/codetide/agents/tide/agent.py b/codetide/agents/tide/agent.py index 5c88713..8713264 100644 --- a/codetide/agents/tide/agent.py +++ b/codetide/agents/tide/agent.py @@ -1,5 +1,6 @@ from codetide import CodeTide from ...mcp.tools.patch_code import file_exists, open_file, process_patch, remove_file, write_file +from ...autocomplete import AutoComplete from .prompts import ( AGENT_TIDE_SYSTEM_PROMPT, GET_CODE_IDENTIFIERS_SYSTEM_PROMPT, WRITE_PATCH_SYSTEM_PROMPT ) @@ -41,7 +42,7 @@ async def agent_loop(self): include_types=True ) - code_identifiers = await self.llm.acomplete( + codeIdentifiers = await self.llm.acomplete( self.history, system_prompt=[GET_CODE_IDENTIFIERS_SYSTEM_PROMPT.format(DATE=TODAY)], prefix_prompt=repo_tree, @@ -49,12 +50,20 @@ async def agent_loop(self): json_output=True ) - # diffPatches = None codeContext = None - if code_identifiers: - codeContext = self.tide.get(code_identifiers, as_string=True) - - + if codeIdentifiers: + autocomplete = AutoComplete(self.tide.cached_ids) + # Validate each code identifier + validatedCodeIdentifiers = [] + for codeId in codeIdentifiers: + result = autocomplete.validate_code_identifier(codeId) + if result.get("is_valid"): + validatedCodeIdentifiers.append(codeId) + + elif result.get("matching_identifiers"): + validatedCodeIdentifiers.append(result.get("matching_identifiers")[0]) + + codeContext = self.tide.get(validatedCodeIdentifiers, as_string=True) response = await self.llm.acomplete( self.history, diff --git a/codetide/agents/tide/prompts.py b/codetide/agents/tide/prompts.py index 0f99d3f..ff873a3 100644 --- a/codetide/agents/tide/prompts.py +++ b/codetide/agents/tide/prompts.py @@ -116,9 +116,8 @@ RESPONSE FORMAT (ALWAYS): - - or -, follow with a complete and valid patch block> + + --- @@ -186,9 +185,9 @@ PATCH STRUCTURE RULES: -* Use one \*\*\* \[ACTION] File: block per file +* Use one *** [ACTION] File: block per file - * \[ACTION] must be one of Add, Update, or Delete + * [ACTION] must be one of Add, Update, or Delete * Inside each file patch: @@ -239,7 +238,7 @@ 2. Ensure one patch block per file, using multiple @@ hunks as needed 3. Include no formatting, layout, or interpretation changes 4. Ensure every @@ header is a valid, real, byte-identical line from the original file -5. Match the structure of the apply\_patch tool’s expectations exactly +5. Match the `MANDATORY PATCH FORMAT (V4A-Compatible)` structure expectations exactly 6. Ensure each patch line starts with a `@`, `+`, `-` or ` ` This is a surgical, precision editing mode. From fb6a02dd682a1ffa70ef88b5fb200b14d178d459 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 11:36:54 +0100 Subject: [PATCH 69/88] feat: add comprehensive tests for TypeScriptParser handling of decorators, modifiers, and multiple declarations --- tests/parsers/test_typescript_parser.py | 136 ++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/tests/parsers/test_typescript_parser.py b/tests/parsers/test_typescript_parser.py index 42a8dea..d7289e8 100644 --- a/tests/parsers/test_typescript_parser.py +++ b/tests/parsers/test_typescript_parser.py @@ -13,6 +13,142 @@ def parser() -> TypeScriptParser: class TestTypeScriptParser: + def test_function_with_decorators_and_modifiers(self, parser: TypeScriptParser): + code = """ +@decorator1 +@decorator2 +export async function myFunc(a: number, b: string = "default"): Promise { + return [b]; +} +""" + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + assert len(code_file.functions) == 1 + func = code_file.functions[0] + # Decorators are not yet parsed by TypeScriptParser, but modifiers should be present + assert "async" in func.modifiers or "export" in func.modifiers + sig = func.signature + assert sig is not None + assert sig.return_type is not None + assert len(sig.parameters) == 2 + param1 = sig.parameters[0] + assert param1.name == "a" + assert param1.type_hint == "number" + assert param1.default_value is None + param2 = sig.parameters[1] + assert param2.name == "b" + assert param2.type_hint == "string" + assert param2.default_value == '"default"' + + def test_class_with_static_and_access_modifiers(self, parser: TypeScriptParser): + code = """ +class Modifiers { + public static count: number = 0; + private _name: string; + protected flag: boolean = true; + constructor(name: string) { + this._name = name; + } + static getCount(): number { + return Modifiers.count; + } + public get name(): string { + return this._name; + } +} +""" + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + assert len(code_file.classes) == 1 + cls = code_file.classes[0] + attr_names = {a.name for a in cls.attributes} + assert "count" in attr_names + assert "flag" in attr_names + assert "_name" in attr_names + count_attr = next(a for a in cls.attributes if a.name == "count") + assert "static" in count_attr.modifiers or "public" in count_attr.modifiers + flag_attr = next(a for a in cls.attributes if a.name == "flag") + assert "protected" in flag_attr.modifiers + methods = {m.name: m for m in cls.methods} + assert "getCount" in methods + assert "name" in methods + get_count = methods["getCount"] + assert "static" in get_count.modifiers + get_name = methods["name"] + assert "public" in get_name.modifiers or get_name.modifiers == [] + + def test_multiple_variable_declarations(self, parser: TypeScriptParser): + code = "let a = 1, b: string = 'hi', c;" + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + # The parser currently only supports one variable per declaration, so at least one should be present + assert any(v.name == "a" for v in code_file.variables) + assert any(v.name == "b" for v in code_file.variables) + assert any(v.name == "c" for v in code_file.variables) + + def test_multiple_imports_and_aliases(self, parser: TypeScriptParser): + code = "import { A, B as Bee, C } from 'mod';" + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + print(f"{code_file=}") + # The parser currently only supports one import per statement, but at least one should be present + assert any(i.name == "A" for i in code_file.imports) + assert any(i.name == "B" and i.alias == "Bee" for i in code_file.imports) + assert any(i.name == "C" for i in code_file.imports) + + def test_arrow_function_and_function_expression(self, parser: TypeScriptParser): + code = """ +const arrow = (x: number): number => x * 2; +const expr = function(y: string): string { return y + "!"; } +""" + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + # The parser may not yet support arrow/functions as top-level, but should at least parse variables + assert any(v.name == "arrow" for v in code_file.variables) + assert any(v.name == "expr" for v in code_file.variables) + + def test_parameter_edge_cases(self, parser: TypeScriptParser): + code = """ +function edge(a, b?: number, c = 5, d: string = "x", ...rest: any[]) {} +""" + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + func = code_file.functions[0] + param_names = [p.name for p in func.signature.parameters] + assert "a" in param_names + assert "b" in param_names + assert "c" in param_names + assert "d" in param_names + # rest parameter may not be parsed, but at least the others should be present + b_param = next(p for p in func.signature.parameters if p.name == "b") + assert b_param.type_hint == "number" + assert b_param.default_value is None + c_param = next(p for p in func.signature.parameters if p.name == "c") + assert c_param.default_value == "5" + d_param = next(p for p in func.signature.parameters if p.name == "d") + assert d_param.type_hint == "string" + assert d_param.default_value == '"x"' + + def test_class_with_method_and_attribute_references(self, parser: TypeScriptParser): + code = """ +class Helper { + doWork() { return 1; } + value: number = 42; +} +function useHelper(h: Helper): number { + return h.doWork() + h.value; +} +""" + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + from codetide.core.models import CodeBase + parser.resolve_intra_file_dependencies(CodeBase(root=[code_file])) + func = code_file.get("test.useHelper") + assert func is not None + ref_names = {ref.name for ref in func.references} + assert "doWork" in ref_names + assert "value" in ref_names + def test_initialization(self, parser: TypeScriptParser): """Tests the basic properties and initialization of the parser.""" assert parser.language == "typescript" From d5e479ff4018f9a6939274d01cba6ebe111feb62 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 11:49:10 +0100 Subject: [PATCH 70/88] feat: enhance patch processing to handle specific line prefixes in text_to_patch --- codetide/mcp/tools/patch_code/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codetide/mcp/tools/patch_code/__init__.py b/codetide/mcp/tools/patch_code/__init__.py index b2f10a7..7a329e0 100644 --- a/codetide/mcp/tools/patch_code/__init__.py +++ b/codetide/mcp/tools/patch_code/__init__.py @@ -21,6 +21,8 @@ def text_to_patch(text: str, orig: Dict[str, str]) -> Tuple[Patch, int]: lines = text.splitlines() for i, line in enumerate(lines): if line.startswith(("@", "***")): + if line.startswith("@@") and lines[i+1].startswith("+"): + lines.insert(i+1, line.replace("@@", "")) continue elif line.startswith("---") or not line.startswith(("+", "-", " ")): From 152051f5a5431bb549aa04195f5ce863fd200ed4 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 17:54:08 +0100 Subject: [PATCH 71/88] feat: add support for processing interface declarations and enhance class node handling in TypeScriptParser --- codetide/parsers/typescript_parser.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/codetide/parsers/typescript_parser.py b/codetide/parsers/typescript_parser.py index f2cf7ba..4f4b7d4 100644 --- a/codetide/parsers/typescript_parser.py +++ b/codetide/parsers/typescript_parser.py @@ -142,6 +142,8 @@ def _process_node(cls, node: Node, code: bytes, codeFile: CodeFileModel): cls._process_variable_declaration(child, code, codeFile) elif child.type == "expression_statement": cls._process_expression_statement(child, code, codeFile) + elif child.type == "interface_declaration": + cls._process_class_node(node, code, codeFile, "interface") @classmethod def _process_import_clause_node(cls, node: Node, code: bytes) -> Tuple[List[str], List[Optional[str]]]: @@ -224,21 +226,26 @@ def _process_import_node(cls, node: Node, code: bytes, codeFile: CodeFileModel): cls._generate_unique_import_id(codeFile.imports[-1]) @classmethod - def _process_class_node(cls, node: Node, code: bytes, codeFile: CodeFileModel): - # TODO add support for modifiers at variables, classes + def _process_class_node(cls, node: Node, code: bytes, codeFile: CodeFileModel, node_type :Literal["class", "interface", "type"]="class"): + # TODO add support for modifiers at variables, classes i.e class_name = None bases = [] raw = cls._get_content(code, node) for child in node.children: if child.type == "type_identifier" and class_name is None: class_name = cls._get_content(code, child) - elif child.type == "class_heritage": + elif child.type == f"{node_type}_heritage": for base_child in child.children: if base_child.type == "extends_clause": for expr_child in base_child.children: if expr_child.type == "identifier": bases.append(cls._get_content(code, expr_child)) - elif child.type == "class_body": + elif child.type == "extends_type_clause": + for base_child in child.children: + if base_child.type == "type_identifier": + bases.append(cls._get_content(code, base_child)) + + elif child.type == f"{node_type}_body": class_def = ClassDefinition( name=class_name, bases=bases, @@ -250,9 +257,9 @@ def _process_class_node(cls, node: Node, code: bytes, codeFile: CodeFileModel): @classmethod def _process_class_body(cls, node: Node, code: bytes, codeFile: CodeFileModel): for child in node.children: - if child.type == "method_definition": + if child.type == "method_definition" or child.type == "method_signature": cls._process_function_definition(child, code, codeFile, is_method=True) - elif child.type == "public_field_definition": + elif child.type == "public_field_definition" or child.type == "property_signature" or child.type == "index_signature": cls._process_class_attribute(child, code, codeFile) @classmethod @@ -264,7 +271,7 @@ def _process_class_attribute(cls, node: Node, code: bytes, codeFile: CodeFileMod next_is_assignment = False raw = cls._get_content(code, node) for child in node.children: - if child.type == "property_identifier" and attribute is None: + if child.type == "property_identifier" or child.type == "identifier" and attribute is None: attribute = cls._get_content(code, child) elif child.type == "type_annotation": type_hint = cls._get_content(code, child).replace(": ", "") From 39fe2a2021b555139c7253cc23f13b61c60a0585 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 17:54:31 +0100 Subject: [PATCH 72/88] feat: add tests for TypeScriptParser handling of interfaces, methods, and index signatures --- tests/parsers/test_typescript_parser.py | 82 +++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/tests/parsers/test_typescript_parser.py b/tests/parsers/test_typescript_parser.py index d7289e8..b019ceb 100644 --- a/tests/parsers/test_typescript_parser.py +++ b/tests/parsers/test_typescript_parser.py @@ -13,6 +13,88 @@ def parser() -> TypeScriptParser: class TestTypeScriptParser: + def test_simple_interface(self, parser: TypeScriptParser): + code = """ +interface Person { + name: string; + age: number; +} +const user: Person = { + name: 'Alice', + age: 30 +}; +""" + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + # Assert that the interface is stored as a class + assert any(cls.name == "Person" for cls in code_file.classes) + person_cls = next(cls for cls in code_file.classes if cls.name == "Person") + attr_names = {a.name for a in person_cls.attributes} + assert "name" in attr_names + assert "age" in attr_names + + + def test_interface_with_method(self, parser: TypeScriptParser): + code = """ +interface Greeter { + greet(): void; +} +const bot: Greeter = { + greet() { + console.log("Hello!"); + } +}; +""" + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + # Assert that the interface is stored as a class + assert any(cls.name == "Greeter" for cls in code_file.classes) + greeter_cls = next(cls for cls in code_file.classes if cls.name == "Greeter") + method_names = {m.name for m in greeter_cls.methods} + assert "greet" in method_names + + def test_interface_with_methods(self, parser: TypeScriptParser): + code = """ +interface Greeter { + greet(message: string): void; + getName(): string; +} +""" + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + # Assert that the interface is stored as a class + assert any(cls.name == "Greeter" for cls in code_file.classes) + greeter_cls = next(cls for cls in code_file.classes if cls.name == "Greeter") + method_names = {m.name for m in greeter_cls.methods} + assert "greet" in method_names + assert "getName" in method_names + + def test_interface_with_index_signature(self, parser: TypeScriptParser): + code = """ +interface StringArray { + [index: number]: string; +} +""" + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + # Assert that the interface is stored as a class + assert any(cls.name == "StringArray" for cls in code_file.classes) + sa_cls = next(cls for cls in code_file.classes if cls.name == "StringArray") + # Should have at least one attribute for the index signature + assert any("index" in a.name or "[" in getattr(a, "raw", "") for a in sa_cls.attributes) + + def test_interface_extends(self, parser: TypeScriptParser): + code = """ +interface A { a: number; } +interface B extends A { b: string; } +""" + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + # Assert that the interface is stored as a class + assert any(cls.name == "B" for cls in code_file.classes) + b_cls = next(cls for cls in code_file.classes if cls.name == "B") + assert "A" in b_cls.bases + def test_function_with_decorators_and_modifiers(self, parser: TypeScriptParser): code = """ @decorator1 From 29512a097e694b890f1e10454252b3b958f43148 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 18:08:12 +0100 Subject: [PATCH 73/88] fix: correct parameter reference in interface declaration processing --- codetide/parsers/typescript_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codetide/parsers/typescript_parser.py b/codetide/parsers/typescript_parser.py index 4f4b7d4..1c48116 100644 --- a/codetide/parsers/typescript_parser.py +++ b/codetide/parsers/typescript_parser.py @@ -143,7 +143,7 @@ def _process_node(cls, node: Node, code: bytes, codeFile: CodeFileModel): elif child.type == "expression_statement": cls._process_expression_statement(child, code, codeFile) elif child.type == "interface_declaration": - cls._process_class_node(node, code, codeFile, "interface") + cls._process_class_node(child, code, codeFile, "interface") @classmethod def _process_import_clause_node(cls, node: Node, code: bytes) -> Tuple[List[str], List[Optional[str]]]: From 81b46dce67fc3d028e653dc52dc72efd200ea403 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 18:36:33 +0100 Subject: [PATCH 74/88] feat: implement processing for interface and type alias declarations in TypeScriptParser --- codetide/parsers/typescript_parser.py | 41 +++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/codetide/parsers/typescript_parser.py b/codetide/parsers/typescript_parser.py index 1c48116..2fa7faf 100644 --- a/codetide/parsers/typescript_parser.py +++ b/codetide/parsers/typescript_parser.py @@ -127,6 +127,27 @@ def _process_export_statement(cls, node: Node, code: bytes, codeFile: CodeFileMo elif child.type == "expression_statement": cls._process_expression_statement(child, code, codeFile) + @classmethod + def _process_interface_declaration(cls, node: Node, code: bytes, codeFile: CodeFileModel)->Tuple[List[str], List[str]]: + # for child in node.children: + # print(f"{child.type=}, {cls._get_content(code, child)}") + + cls._process_class_node(node, code, codeFile, "interface") + # if child.type == "decorator": + # decorators.append(cls._get_content(code, child)) + + # elif child.type == "export": + # modifiers.append(cls._get_content(code, child)) + + # elif child.type == "function_declaration": + # cls._process_function_definition(child, code, codeFile, modifiers=modifiers, decorators=decorators) + + # elif child.type == "class_declaration": + # cls._process_class_node(child, code, codeFile) + + # elif child.type == "expression_statement": + # cls._process_expression_statement(child, code, codeFile) + @classmethod def _process_node(cls, node: Node, code: bytes, codeFile: CodeFileModel): for child in node.children: @@ -144,6 +165,8 @@ def _process_node(cls, node: Node, code: bytes, codeFile: CodeFileModel): cls._process_expression_statement(child, code, codeFile) elif child.type == "interface_declaration": cls._process_class_node(child, code, codeFile, "interface") + elif child.type == "type_alias_declaration": + cls._process_class_node(child, code, codeFile, "type") @classmethod def _process_import_clause_node(cls, node: Node, code: bytes) -> Tuple[List[str], List[Optional[str]]]: @@ -234,18 +257,32 @@ def _process_class_node(cls, node: Node, code: bytes, codeFile: CodeFileModel, n for child in node.children: if child.type == "type_identifier" and class_name is None: class_name = cls._get_content(code, child) - elif child.type == f"{node_type}_heritage": + elif child.type == f"{node_type}_heritage" or child.type == "intersection_type": for base_child in child.children: if base_child.type == "extends_clause": for expr_child in base_child.children: if expr_child.type == "identifier": bases.append(cls._get_content(code, expr_child)) + + if base_child.type == "type_identifier": + bases.append(cls._get_content(code, base_child)) + + elif base_child.type == "object_type": + class_def = ClassDefinition( + name=class_name, + bases=bases, + raw=raw + ) + codeFile.add_class(class_def) + cls._process_class_body(base_child, code, codeFile) + elif child.type == "extends_type_clause": for base_child in child.children: if base_child.type == "type_identifier": bases.append(cls._get_content(code, base_child)) - elif child.type == f"{node_type}_body": + + elif child.type == f"{node_type}_body" or child.type == "object_type": class_def = ClassDefinition( name=class_name, bases=bases, From 2f70b9d138c263e5e0065f79d11e51fcc257a61b Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 18:36:48 +0100 Subject: [PATCH 75/88] feat: add tests for type aliases and interface extensions in TypeScriptParser --- tests/parsers/test_typescript_parser.py | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/parsers/test_typescript_parser.py b/tests/parsers/test_typescript_parser.py index b019ceb..ed0329c 100644 --- a/tests/parsers/test_typescript_parser.py +++ b/tests/parsers/test_typescript_parser.py @@ -13,6 +13,51 @@ def parser() -> TypeScriptParser: class TestTypeScriptParser: + def test_simple_type_alias(self, parser: TypeScriptParser): + code = """ +type User = { + id: number; + username: string; +}; +const u: User = { id: 1, username: "bob" }; +""" + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + assert any(cls.name == "User" for cls in code_file.classes) + user_cls = next(cls for cls in code_file.classes if cls.name == "User") + attr_names = {a.name for a in user_cls.attributes} + assert "id" in attr_names + assert "username" in attr_names + + def test_type_with_methods(self, parser: TypeScriptParser): + code = """ +type Logger = { + log(message: string): void; + error(msg: string): void; +}; +""" + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + assert any(cls.name == "Logger" for cls in code_file.classes) + logger_cls = next(cls for cls in code_file.classes if cls.name == "Logger") + method_names = {m.name for m in logger_cls.methods} + assert "log" in method_names + assert "error" in method_names + + def test_type_extends_interface(self, parser: TypeScriptParser): + code = """ +interface Base { base: number; } +type Extended = Base & { extra: string; }; +""" + file_path = Path("test.ts") + code_file = parser.parse_code(code.encode('utf-8'), file_path) + assert any(cls.name == "Extended" for cls in code_file.classes) + extended_cls = next(cls for cls in code_file.classes if cls.name == "Extended") + # Should have "Base" as a base (from intersection) + assert any("Base" in b for b in extended_cls.bases) + attr_names = {a.name for a in extended_cls.attributes} + assert "extra" in attr_names + def test_simple_interface(self, parser: TypeScriptParser): code = """ interface Person { From 050d319c9e048f2664c95902f2c17c85758fc17a Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 19:00:22 +0100 Subject: [PATCH 76/88] feat: enhance newline replacement to exclude triple-quoted strings in replace_newline_in_quotes function --- codetide/mcp/tools/patch_code/__init__.py | 36 ++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/codetide/mcp/tools/patch_code/__init__.py b/codetide/mcp/tools/patch_code/__init__.py index 7a329e0..6cb633d 100644 --- a/codetide/mcp/tools/patch_code/__init__.py +++ b/codetide/mcp/tools/patch_code/__init__.py @@ -102,9 +102,29 @@ def apply_commit( remove_fn(path) def replace_newline_in_quotes(text, token=BREAKLINE_TOKEN): + """ + Replace newlines with a special token only within single/double quoted strings, + but NOT within triple-quoted strings. + """ + + # First, let's handle triple-quoted strings by temporarily replacing them + # with placeholders to avoid processing them + triple_quote_placeholders = [] + + # Find all triple-quoted strings (both ''' and """) + triple_pattern = r'(""".*?"""|\'\'\'.*?\'\'\')' + + def store_triple_quote(match): + placeholder = f"__TRIPLE_QUOTE_{len(triple_quote_placeholders)}__" + triple_quote_placeholders.append(match.group(1)) + return placeholder + + # Temporarily replace triple quotes with placeholders + text_with_placeholders = re.sub(triple_pattern, store_triple_quote, text, flags=re.DOTALL) + + # Now process single/double quoted strings (excluding triple quotes) pattern = r''' - (? Date: Sat, 19 Jul 2025 19:00:37 +0100 Subject: [PATCH 77/88] feat: add comprehensive test suite for replace_newline_in_quotes function --- .../tools/test_replace_newline_in_quotes.py | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 tests/mcp/tools/test_replace_newline_in_quotes.py diff --git a/tests/mcp/tools/test_replace_newline_in_quotes.py b/tests/mcp/tools/test_replace_newline_in_quotes.py new file mode 100644 index 0000000..e475d7b --- /dev/null +++ b/tests/mcp/tools/test_replace_newline_in_quotes.py @@ -0,0 +1,189 @@ +from codetide.mcp.tools.patch_code import BREAKLINE_TOKEN, replace_newline_in_quotes + +class TestReplaceNewlineInQuotes: + """Test suite for replace_newline_in_quotes function""" + + def test_single_quoted_string_with_newline(self): + """Test replacement in single-quoted strings with actual newlines""" + text = "'Hello\nWorld'" + expected = f"'Hello{BREAKLINE_TOKEN}World'" + result = replace_newline_in_quotes(text) + assert result == expected + + def test_double_quoted_string_with_newline(self): + """Test replacement in double-quoted strings with actual newlines""" + text = '"Hello\nWorld"' + expected = f'"Hello{BREAKLINE_TOKEN}World"' + result = replace_newline_in_quotes(text) + assert result == expected + + def test_single_quoted_string_with_literal_backslash_n(self): + """Test replacement of literal \\n in single-quoted strings""" + text = r"'Hello\nWorld'" + expected = f"'Hello{BREAKLINE_TOKEN}World'" + result = replace_newline_in_quotes(text) + assert result == expected + + def test_double_quoted_string_with_literal_backslash_n(self): + """Test replacement of literal \\n in double-quoted strings""" + text = r'"Hello\nWorld"' + expected = f'"Hello{BREAKLINE_TOKEN}World"' + result = replace_newline_in_quotes(text) + assert result == expected + + def test_triple_quoted_strings_unchanged(self): + """Test that triple-quoted strings are not modified""" + text = '''"""This has a +newline that should NOT change"""''' + result = replace_newline_in_quotes(text) + assert result == text + + def test_triple_single_quoted_strings_unchanged(self): + """Test that triple single-quoted strings are not modified""" + text = """'''This has a +newline that should NOT change'''""" + result = replace_newline_in_quotes(text) + assert result == text + + def test_mixed_quote_types(self): + """Test handling of mixed single and double quotes""" + text = '''single = 'Hello\\nWorld' +double = "Goodbye\\nMoon"''' + expected = f'''single = 'Hello{BREAKLINE_TOKEN}World' +double = "Goodbye{BREAKLINE_TOKEN}Moon"''' + result = replace_newline_in_quotes(text) + assert result == expected + + def test_escaped_quotes_in_strings(self): + """Test strings containing escaped quotes""" + text = r"'He said \"Hello\nWorld\"'" + expected = f"'He said \\\"Hello{BREAKLINE_TOKEN}World\\\"'" + result = replace_newline_in_quotes(text) + assert result == expected + + def test_nested_quotes_scenario(self): + """Test complex nested quote scenarios""" + text = '''"She said 'Hello\\nWorld' to me"''' + expected = f'''"She said 'Hello{BREAKLINE_TOKEN}World' to me"''' + result = replace_newline_in_quotes(text) + assert result == expected + + def test_multiple_newlines_in_single_string(self): + """Test multiple newlines within a single quoted string""" + text = "'First\\nSecond\\nThird'" + expected = f"'First{BREAKLINE_TOKEN}Second{BREAKLINE_TOKEN}Third'" + result = replace_newline_in_quotes(text) + assert result == expected + + def test_empty_quoted_strings(self): + """Test empty quoted strings""" + text = "''" + result = replace_newline_in_quotes(text) + assert result == text + + text = '""' + result = replace_newline_in_quotes(text) + assert result == text + + def test_no_quotes_in_text(self): + """Test text with no quotes at all""" + text = "This is plain text with\nnewlines" + result = replace_newline_in_quotes(text) + assert result == text + + def test_unmatched_quotes(self): + """Test text with unmatched quotes (should not be processed)""" + text = "This has an unmatched ' quote" + result = replace_newline_in_quotes(text) + assert result == text + + def test_custom_token(self): + """Test using a custom replacement token""" + text = "'Hello\\nWorld'" + custom_token = "<>" + expected = f"'Hello{custom_token}World'" + result = replace_newline_in_quotes(text, token=custom_token) + assert result == expected + + def test_function_definition_scenario(self): + """Test the original failing scenario from your example""" + text = '''def example(): + single = 'This has a +newline' + double = "This also has a +newline" + triple = """This is a triple quote +with multiple +lines that should NOT be changed""" + another_triple = \'\'\'Another triple +with newlines +that should stay\'\'\' + return single, double, triple''' + + result = replace_newline_in_quotes(text) + + # Check that single and double quoted strings are modified + assert f"'This has a{BREAKLINE_TOKEN}newline'" in result + assert f'"This also has a{BREAKLINE_TOKEN}newline"' in result + + # Check that triple quoted strings remain unchanged + assert '''"""This is a triple quote +with multiple +lines that should NOT be changed"""''' in result + assert '''\'\'\'Another triple +with newlines +that should stay\'\'\'''' in result + + def test_consecutive_quoted_strings(self): + """Test consecutive quoted strings""" + text = "'First\\nString' 'Second\\nString'" + expected = f"'First{BREAKLINE_TOKEN}String' 'Second{BREAKLINE_TOKEN}String'" + result = replace_newline_in_quotes(text) + assert result == expected + + def test_mixed_actual_and_literal_newlines(self): + """Test strings with both actual newlines and literal \\n""" + text = """'Hello\\nWorld +Real newline here'""" + expected = f"""'Hello{BREAKLINE_TOKEN}World{BREAKLINE_TOKEN}Real newline here'""" + result = replace_newline_in_quotes(text) + assert result == expected + + def test_quotes_within_triple_quotes(self): + """Test single/double quotes within triple quotes (should not be processed)""" + text = '''"""This contains 'single quotes' and "double quotes" +with newlines that should NOT change"""''' + result = replace_newline_in_quotes(text) + assert result == text + + def test_complex_python_code(self): + """Test a complex Python code snippet""" + text = '''def process_data(): + query = "SELECT * FROM users WHERE name = 'John\\nDoe'" + error_msg = 'Database connection failed\\nRetrying...' + docstring = """ + This function processes data from the database. + It handles errors gracefully and logs appropriate messages. + No changes should be made to this docstring. + """ + return query, error_msg''' + + result = replace_newline_in_quotes(text) + + # Check replacements in single/double quotes + assert f'"SELECT * FROM users WHERE name = \'John{BREAKLINE_TOKEN}Doe\'"' in result + assert f"'Database connection failed{BREAKLINE_TOKEN}Retrying...'" in result + + # Check triple quotes unchanged + assert '''""" + This function processes data from the database. + It handles errors gracefully and logs appropriate messages. + No changes should be made to this docstring. + """''' in result + + def test_edge_case_almost_triple_quotes(self): + """Test strings that look almost like triple quotes but aren't""" + text = "'' + '\\ntest'" # Two single quotes followed by another string + expected = f"'' + '{BREAKLINE_TOKEN}test'" + result = replace_newline_in_quotes(text) + assert result == expected \ No newline at end of file From 394c107aa0f9f439d22b1d658598a776321127c5 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 19:14:01 +0100 Subject: [PATCH 78/88] fix: refine line handling in text_to_patch to correctly process specific line formats --- codetide/mcp/tools/patch_code/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codetide/mcp/tools/patch_code/__init__.py b/codetide/mcp/tools/patch_code/__init__.py index 6cb633d..4a60f35 100644 --- a/codetide/mcp/tools/patch_code/__init__.py +++ b/codetide/mcp/tools/patch_code/__init__.py @@ -25,7 +25,7 @@ def text_to_patch(text: str, orig: Dict[str, str]) -> Tuple[Patch, int]: lines.insert(i+1, line.replace("@@", "")) continue - elif line.startswith("---") or not line.startswith(("+", "-", " ")): + elif (line.startswith("---") and len(line)==3) or not line.startswith(("+", "-", " ")): lines[i] = f" {line}" # print(f"\n\n{lines[-2:]=}") From 4b5198539072a207fc5ff0080129a1edbbc6cb80 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 19:16:15 +0100 Subject: [PATCH 79/88] feat: enhance identifier resolution prompt to prioritize code identifiers over file paths --- codetide/agents/tide/prompts.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/codetide/agents/tide/prompts.py b/codetide/agents/tide/prompts.py index ff873a3..13f4546 100644 --- a/codetide/agents/tide/prompts.py +++ b/codetide/agents/tide/prompts.py @@ -47,7 +47,7 @@ """ GET_CODE_IDENTIFIERS_SYSTEM_PROMPT = """ -You are Agent **Tide**, operating in **Identifier Resolution Mode** on **{DATE}**. You have received a user request and a visual representation of the code repository structure. Your task is to determine which files or code-level identifiers (such as functions, classes, methods, variables) are relevant for fulfilling the request. +You are Agent **Tide**, operating in **Identifier Resolution Mode** on **{DATE}**. You have received a user request and a visual representation of the code repository structure. Your task is to determine which code-level identifiers (such as functions, classes, methods, variables, or attributes) or, if necessary, file paths are relevant for fulfilling the request. You are operating under a strict **single-call constraint**: the repository tree structure (via `getRepoTree()`) can only be retrieved **once per task**, and you must extract maximum value from it. Do **not** request the tree again under any circumstances. @@ -56,19 +56,22 @@ **Instructions:** 1. Carefully read and interpret the user's request, identifying any references to files, modules, submodules, or code elements—either explicit or implied. -2. If the user refers to a file by name or path (e.g., requesting changes, updates, rewrites, or additions), you **must include that file path** as a string identifier in the output. -3. If any symbols within those files (functions, methods, classes, variables) are likely to be involved in the task or can be used as context, include their fully qualified identifiers. -4. If fulfilling the request would likely depend on additional symbols or files—based on naming, structure, require context from other files / modules or conventional design patterns—include those as well. -5. Only include identifiers or paths that are present in the provided tree structure. Never fabricate or guess paths or names that do not exist. -6. If no relevant files or symbols can be confidently identified, return an empty list. +2. **Prioritize returning fully qualified code identifiers** (such as functions, classes, methods, variables, or attributes) that are directly related to the user's request or are elements of interest. The identifier format must use dot notation to represent the path-like structure, e.g., `module.submodule.Class.method` or `module.function`, without file extensions. +3. Only include full file paths (relative to the repository root) if: + - The user explicitly requests file-level operations (such as adding, deleting, or renaming files), or + - No valid or relevant code identifiers can be determined for the request. +4. If the user refers to a file by name or path and the request is about code elements within that file, extract and include the relevant code identifiers from that file instead of the file path, unless the user specifically asks for the file path. +5. If fulfilling the request would likely depend on additional symbols or files—based on naming, structure, required context from other files/modules, or conventional design patterns—include those code identifiers as well. +6. Only include identifiers or paths that are present in the provided tree structure. Never fabricate or guess paths or names that do not exist. +7. If no relevant code identifiers or file paths can be confidently identified, return an empty list. --- **Output Format (Strict JSON Only):** Return a JSON array of strings. Each string must be: -- A valid file path relative to the repository root -- Or a fully qualified code identifier, including the full path and symbol name +- A fully qualified code identifier using dot notation (e.g., `module.submodule.Class.method`), without file extensions, or +- A valid file path relative to the repository root (only if explicitly required or no code identifiers are available). Your output must be a pure JSON list of strings. Do **not** include any explanation, comments, or formatting outside the JSON block. @@ -76,11 +79,11 @@ **Evaluation Criteria:** -- You must identify all files directly referenced or implied in the user request. +- You must identify all code identifiers directly referenced or implied in the user request, prioritizing them over file paths. - You must include any internal code elements that are clearly involved or required for the task. - You must consider logical dependencies that may need to be modified together (e.g., helper modules, config files, related class methods). -- You must consider files that can be relevant as context to complete the user request. -- You must return a clean and complete list of all relevant file paths and symbols. +- You must consider files that can be relevant as context to complete the user request, but only include their paths if code identifiers are not available or explicitly requested. +- You must return a clean and complete list of all relevant code identifiers and, if necessary, file paths. - Do not over-include; be minimal but thorough. Return only what is truly required. """ From de0bb102b38b29d54a3f0e8f80550d560e2da9d7 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sat, 19 Jul 2025 23:58:45 +0100 Subject: [PATCH 80/88] updated requirements-agents --- requirements-agents.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-agents.txt b/requirements-agents.txt index 329940a..8ecf045 100644 --- a/requirements-agents.txt +++ b/requirements-agents.txt @@ -1 +1 @@ -core-for-ai>=0.1.97 \ No newline at end of file +core-for-ai>=0.1.98 \ No newline at end of file From 3bf9e9dd29e4d6eee81de9527f3c9af936f4b54f Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 20 Jul 2025 00:28:04 +0100 Subject: [PATCH 81/88] . --- requirements-agents.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-agents.txt b/requirements-agents.txt index 8ecf045..b3e1759 100644 --- a/requirements-agents.txt +++ b/requirements-agents.txt @@ -1 +1 @@ -core-for-ai>=0.1.98 \ No newline at end of file +core-for-ai>=0.1.98 From e535b0cf3c8bd3285bcb02a04f5f5e0d1b642d92 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 20 Jul 2025 00:29:41 +0100 Subject: [PATCH 82/88] updated agents reqs --- requirements-agents.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-agents.txt b/requirements-agents.txt index b3e1759..2cd8bbe 100644 --- a/requirements-agents.txt +++ b/requirements-agents.txt @@ -1 +1,2 @@ core-for-ai>=0.1.98 +prompt_toolkit==3.0.50 \ No newline at end of file From 5f764579ce77c041fd6f6902897b935dea3472cd Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 20 Jul 2025 12:47:09 +0100 Subject: [PATCH 83/88] updated server with serve and serve async methods --- codetide/mcp/server.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/codetide/mcp/server.py b/codetide/mcp/server.py index b37f558..134a54d 100644 --- a/codetide/mcp/server.py +++ b/codetide/mcp/server.py @@ -2,6 +2,16 @@ from fastmcp import FastMCP codeTideMCPServer = FastMCP( - name="CodeTide", + name="codetide", # instructions=AGENT_TIDE_SYSTEM_PROMPT, ) + +def serve(): + codeTideMCPServer.run("stdio") + +async def aserve(): + await codeTideMCPServer.run_stdio_async() + +if __name__ == "__main__": + import asyncio + asyncio.run(serve()) \ No newline at end of file From 2fd66e17b93ce19281e44bc416598040e4a8dfd3 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 20 Jul 2025 12:51:08 +0100 Subject: [PATCH 84/88] added codetide-mcp-server entry point --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b8dad56..f69a669 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ }, entry_points={ "console_scripts": [ - "codetide=codetide.__main__:main", + "codetide-mcp-server=codetide.mcp.server:serve" ] }, classifiers=[ From 9acff453486bef2f8ad41c1efff3e40fbefc1323 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 20 Jul 2025 12:51:30 +0100 Subject: [PATCH 85/88] added pyproject.toml and uv.lock --- pyproject.toml | 61 ++ uv.lock | 1724 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1785 insertions(+) create mode 100644 pyproject.toml create mode 100644 uv.lock diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b4e5922 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,61 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "codetide" +dynamic = ["version"] +description = "CodeTide is a fully local, privacy-preserving tool for parsing and understanding Python codebases using symbolic, structural analysis. No internet, no LLMs, no embeddings - just fast, explainable, and deterministic code intelligence." +readme = "README.md" + +requires-python = ">=3.10" +license = { text = "Apache-2.0" } +authors = [ + { name = "Bruno V.", email = "bruno.vitorino@tecnico.ulisboa.pt" }, +] +classifiers = [ + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", +] +dependencies = [ + "fastmcp==2.9.2", + "loguru==0.7.3", + "orjson==3.10.13", + "pathspec==0.12.1", + "pydantic==2.10.3", + "pygit2==1.18.0", + "pyyaml==6.0.2", + "tree-sitter-python==0.23.6", + "tree-sitter-typescript==0.23.2", + "tree-sitter==0.24.0", + "ulid==1.1", +] + +[project.optional-dependencies] +agents = [ + "core-for-ai>=0.1.98", + "prompt_toolkit==3.0.50", +] +visualization = [ + "networkx==3.4.2", + "numpy==2.2.0", + "plotly==5.24.1", +] + +[project.scripts] +codetide-mcp-server = "codetide.mcp.server:serve" + +[project.urls] +Homepage = "https://github.com/BrunoV21/CodeTide" + +[tool.hatch.version] +path = "codetide/__init__.py" + +[tool.hatch.build.targets.sdist] +include = [ + "/codetide", +] \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..b5b3612 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1724 @@ +version = 1 +revision = 2 +requires-python = ">=3.10" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anthropic" +version = "0.52.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/8a9332f5baf352c272494a9d359863a53385a208954c1a7251a524071930/anthropic-0.52.0.tar.gz", hash = "sha256:f06bc924d7eb85f8a43fe587b875ff58b410d60251b7dc5f1387b322a35bd67b", size = 229372, upload-time = "2025-05-22T16:42:22.044Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/43/172c0031654908bbac2a87d356fff4de1b4947a9b14b9658540b69416417/anthropic-0.52.0-py3-none-any.whl", hash = "sha256:c026daa164f0e3bde36ce9cbdd27f5f1419fff03306be1e138726f42e6a7810f", size = 286076, upload-time = "2025-05-22T16:42:20Z" }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + +[[package]] +name = "authlib" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/a1/d8d1c6f8bc922c0b87ae0d933a8ed57be1bef6970894ed79c2852a153cd3/authlib-1.6.1.tar.gz", hash = "sha256:4dffdbb1460ba6ec8c17981a4c67af7d8af131231b5a36a88a1e8c80c111cdfd", size = 159988, upload-time = "2025-07-20T07:38:42.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/58/cc6a08053f822f98f334d38a27687b69c6655fb05cd74a7a5e70a2aeed95/authlib-1.6.1-py2.py3-none-any.whl", hash = "sha256:e9d2031c34c6309373ab845afc24168fe9e93dc52d252631f52642f21f5ed06e", size = 239299, upload-time = "2025-07-20T07:38:39.259Z" }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, +] + +[[package]] +name = "certifi" +version = "2025.7.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/76/52c535bcebe74590f296d6c77c86dabf761c41980e1347a2422e4aa2ae41/certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995", size = 163981, upload-time = "2025-07-14T03:29:28.449Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/52/34c6cf5bb9285074dc3531c437b3919e825d976fde097a7a73f79e726d03/certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", size = 162722, upload-time = "2025-07-14T03:29:26.863Z" }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191, upload-time = "2024-09-04T20:43:30.027Z" }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592, upload-time = "2024-09-04T20:43:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024, upload-time = "2024-09-04T20:43:34.186Z" }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188, upload-time = "2024-09-04T20:43:36.286Z" }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571, upload-time = "2024-09-04T20:43:38.586Z" }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687, upload-time = "2024-09-04T20:43:40.084Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211, upload-time = "2024-09-04T20:43:41.526Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325, upload-time = "2024-09-04T20:43:43.117Z" }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784, upload-time = "2024-09-04T20:43:45.256Z" }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564, upload-time = "2024-09-04T20:43:46.779Z" }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804, upload-time = "2024-09-04T20:43:48.186Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299, upload-time = "2024-09-04T20:43:49.812Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818, upload-time = "2025-05-02T08:31:46.725Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649, upload-time = "2025-05-02T08:31:48.889Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045, upload-time = "2025-05-02T08:31:50.757Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356, upload-time = "2025-05-02T08:31:52.634Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471, upload-time = "2025-05-02T08:31:56.207Z" }, + { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317, upload-time = "2025-05-02T08:31:57.613Z" }, + { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368, upload-time = "2025-05-02T08:31:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491, upload-time = "2025-05-02T08:32:01.219Z" }, + { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695, upload-time = "2025-05-02T08:32:03.045Z" }, + { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849, upload-time = "2025-05-02T08:32:04.651Z" }, + { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091, upload-time = "2025-05-02T08:32:06.719Z" }, + { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445, upload-time = "2025-05-02T08:32:08.66Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782, upload-time = "2025-05-02T08:32:10.46Z" }, + { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, + { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, + { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, + { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, + { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, + { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, + { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, + { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, + { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, + { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, + { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, + { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "codetide" +source = { editable = "." } +dependencies = [ + { name = "fastmcp" }, + { name = "loguru" }, + { name = "orjson" }, + { name = "pathspec" }, + { name = "pydantic" }, + { name = "pygit2" }, + { name = "pyyaml" }, + { name = "tree-sitter" }, + { name = "tree-sitter-python" }, + { name = "tree-sitter-typescript" }, + { name = "ulid" }, +] + +[package.optional-dependencies] +agents = [ + { name = "core-for-ai" }, + { name = "prompt-toolkit" }, +] +visualization = [ + { name = "networkx" }, + { name = "numpy" }, + { name = "plotly" }, +] + +[package.metadata] +requires-dist = [ + { name = "core-for-ai", marker = "extra == 'agents'", specifier = ">=0.1.98" }, + { name = "fastmcp", specifier = "==2.9.2" }, + { name = "loguru", specifier = "==0.7.3" }, + { name = "networkx", marker = "extra == 'visualization'", specifier = "==3.4.2" }, + { name = "numpy", marker = "extra == 'visualization'", specifier = "==2.2.0" }, + { name = "orjson", specifier = "==3.10.13" }, + { name = "pathspec", specifier = "==0.12.1" }, + { name = "plotly", marker = "extra == 'visualization'", specifier = "==5.24.1" }, + { name = "prompt-toolkit", marker = "extra == 'agents'", specifier = "==3.0.50" }, + { name = "pydantic", specifier = "==2.10.3" }, + { name = "pygit2", specifier = "==1.18.0" }, + { name = "pyyaml", specifier = "==6.0.2" }, + { name = "tree-sitter", specifier = "==0.24.0" }, + { name = "tree-sitter-python", specifier = "==0.23.6" }, + { name = "tree-sitter-typescript", specifier = "==0.23.2" }, + { name = "ulid", specifier = "==1.1" }, +] +provides-extras = ["agents", "visualization"] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "core-for-ai" +version = "0.1.98" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anthropic" }, + { name = "deepseek-tokenizer" }, + { name = "fastmcp" }, + { name = "google-genai" }, + { name = "groq" }, + { name = "json-repair" }, + { name = "loguru" }, + { name = "mistralai" }, + { name = "openai" }, + { name = "pydantic" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "pyyaml" }, + { name = "setuptools" }, + { name = "tenacity" }, + { name = "tiktoken" }, + { name = "ulid" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/f8/9f8d010953ccf7c738af8bbe3565769180ceec14bad40716e6b4b884b0c2/core_for_ai-0.1.98.tar.gz", hash = "sha256:7e629646a4685b658c3848b5a67c5217189fb5cc88bc54a6d51c323c430edbcc", size = 91212, upload-time = "2025-07-19T23:21:56.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/72/5b4e9769d17543edea81d961974b3db048ba3c1e16889b0610c2a174119e/core_for_ai-0.1.98-py3-none-any.whl", hash = "sha256:38ca4d6c9fb2351f8f8e346ce603e5ec95eb4ee0aa39d281154cd5286f5a7eab", size = 90342, upload-time = "2025-07-19T23:21:55.248Z" }, +] + +[[package]] +name = "cryptography" +version = "45.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/1e/49527ac611af559665f71cbb8f92b332b5ec9c6fbc4e88b0f8e92f5e85df/cryptography-45.0.5.tar.gz", hash = "sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a", size = 744903, upload-time = "2025-07-02T13:06:25.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/fb/09e28bc0c46d2c547085e60897fea96310574c70fb21cd58a730a45f3403/cryptography-45.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:101ee65078f6dd3e5a028d4f19c07ffa4dd22cce6a20eaa160f8b5219911e7d8", size = 7043092, upload-time = "2025-07-02T13:05:01.514Z" }, + { url = "https://files.pythonhosted.org/packages/b1/05/2194432935e29b91fb649f6149c1a4f9e6d3d9fc880919f4ad1bcc22641e/cryptography-45.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d", size = 4205926, upload-time = "2025-07-02T13:05:04.741Z" }, + { url = "https://files.pythonhosted.org/packages/07/8b/9ef5da82350175e32de245646b1884fc01124f53eb31164c77f95a08d682/cryptography-45.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5", size = 4429235, upload-time = "2025-07-02T13:05:07.084Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e1/c809f398adde1994ee53438912192d92a1d0fc0f2d7582659d9ef4c28b0c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57", size = 4209785, upload-time = "2025-07-02T13:05:09.321Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8b/07eb6bd5acff58406c5e806eff34a124936f41a4fb52909ffa4d00815f8c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0", size = 3893050, upload-time = "2025-07-02T13:05:11.069Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ef/3333295ed58d900a13c92806b67e62f27876845a9a908c939f040887cca9/cryptography-45.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d", size = 4457379, upload-time = "2025-07-02T13:05:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9d/44080674dee514dbb82b21d6fa5d1055368f208304e2ab1828d85c9de8f4/cryptography-45.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9", size = 4209355, upload-time = "2025-07-02T13:05:15.017Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d8/0749f7d39f53f8258e5c18a93131919ac465ee1f9dccaf1b3f420235e0b5/cryptography-45.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27", size = 4456087, upload-time = "2025-07-02T13:05:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/09/d7/92acac187387bf08902b0bf0699816f08553927bdd6ba3654da0010289b4/cryptography-45.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e", size = 4332873, upload-time = "2025-07-02T13:05:18.743Z" }, + { url = "https://files.pythonhosted.org/packages/03/c2/840e0710da5106a7c3d4153c7215b2736151bba60bf4491bdb421df5056d/cryptography-45.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174", size = 4564651, upload-time = "2025-07-02T13:05:21.382Z" }, + { url = "https://files.pythonhosted.org/packages/2e/92/cc723dd6d71e9747a887b94eb3827825c6c24b9e6ce2bb33b847d31d5eaa/cryptography-45.0.5-cp311-abi3-win32.whl", hash = "sha256:926c3ea71a6043921050eaa639137e13dbe7b4ab25800932a8498364fc1abec9", size = 2929050, upload-time = "2025-07-02T13:05:23.39Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/197da38a5911a48dd5389c043de4aec4b3c94cb836299b01253940788d78/cryptography-45.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:b85980d1e345fe769cfc57c57db2b59cff5464ee0c045d52c0df087e926fbe63", size = 3403224, upload-time = "2025-07-02T13:05:25.202Z" }, + { url = "https://files.pythonhosted.org/packages/fe/2b/160ce8c2765e7a481ce57d55eba1546148583e7b6f85514472b1d151711d/cryptography-45.0.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3562c2f23c612f2e4a6964a61d942f891d29ee320edb62ff48ffb99f3de9ae8", size = 7017143, upload-time = "2025-07-02T13:05:27.229Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e7/2187be2f871c0221a81f55ee3105d3cf3e273c0a0853651d7011eada0d7e/cryptography-45.0.5-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd", size = 4197780, upload-time = "2025-07-02T13:05:29.299Z" }, + { url = "https://files.pythonhosted.org/packages/b9/cf/84210c447c06104e6be9122661159ad4ce7a8190011669afceeaea150524/cryptography-45.0.5-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e", size = 4420091, upload-time = "2025-07-02T13:05:31.221Z" }, + { url = "https://files.pythonhosted.org/packages/3e/6a/cb8b5c8bb82fafffa23aeff8d3a39822593cee6e2f16c5ca5c2ecca344f7/cryptography-45.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0", size = 4198711, upload-time = "2025-07-02T13:05:33.062Z" }, + { url = "https://files.pythonhosted.org/packages/04/f7/36d2d69df69c94cbb2473871926daf0f01ad8e00fe3986ac3c1e8c4ca4b3/cryptography-45.0.5-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135", size = 3883299, upload-time = "2025-07-02T13:05:34.94Z" }, + { url = "https://files.pythonhosted.org/packages/82/c7/f0ea40f016de72f81288e9fe8d1f6748036cb5ba6118774317a3ffc6022d/cryptography-45.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7", size = 4450558, upload-time = "2025-07-02T13:05:37.288Z" }, + { url = "https://files.pythonhosted.org/packages/06/ae/94b504dc1a3cdf642d710407c62e86296f7da9e66f27ab12a1ee6fdf005b/cryptography-45.0.5-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42", size = 4198020, upload-time = "2025-07-02T13:05:39.102Z" }, + { url = "https://files.pythonhosted.org/packages/05/2b/aaf0adb845d5dabb43480f18f7ca72e94f92c280aa983ddbd0bcd6ecd037/cryptography-45.0.5-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492", size = 4449759, upload-time = "2025-07-02T13:05:41.398Z" }, + { url = "https://files.pythonhosted.org/packages/91/e4/f17e02066de63e0100a3a01b56f8f1016973a1d67551beaf585157a86b3f/cryptography-45.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0", size = 4319991, upload-time = "2025-07-02T13:05:43.64Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2e/e2dbd629481b499b14516eed933f3276eb3239f7cee2dcfa4ee6b44d4711/cryptography-45.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a", size = 4554189, upload-time = "2025-07-02T13:05:46.045Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ea/a78a0c38f4c8736287b71c2ea3799d173d5ce778c7d6e3c163a95a05ad2a/cryptography-45.0.5-cp37-abi3-win32.whl", hash = "sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f", size = 2911769, upload-time = "2025-07-02T13:05:48.329Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/28ac139109d9005ad3f6b6f8976ffede6706a6478e21c889ce36c840918e/cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97", size = 3390016, upload-time = "2025-07-02T13:05:50.811Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8b/34394337abe4566848a2bd49b26bcd4b07fd466afd3e8cce4cb79a390869/cryptography-45.0.5-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:206210d03c1193f4e1ff681d22885181d47efa1ab3018766a7b32a7b3d6e6afd", size = 3575762, upload-time = "2025-07-02T13:05:53.166Z" }, + { url = "https://files.pythonhosted.org/packages/8b/5d/a19441c1e89afb0f173ac13178606ca6fab0d3bd3ebc29e9ed1318b507fc/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c648025b6840fe62e57107e0a25f604db740e728bd67da4f6f060f03017d5097", size = 4140906, upload-time = "2025-07-02T13:05:55.914Z" }, + { url = "https://files.pythonhosted.org/packages/4b/db/daceb259982a3c2da4e619f45b5bfdec0e922a23de213b2636e78ef0919b/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b8fa8b0a35a9982a3c60ec79905ba5bb090fc0b9addcfd3dc2dd04267e45f25e", size = 4374411, upload-time = "2025-07-02T13:05:57.814Z" }, + { url = "https://files.pythonhosted.org/packages/6a/35/5d06ad06402fc522c8bf7eab73422d05e789b4e38fe3206a85e3d6966c11/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:14d96584701a887763384f3c47f0ca7c1cce322aa1c31172680eb596b890ec30", size = 4140942, upload-time = "2025-07-02T13:06:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/65/79/020a5413347e44c382ef1f7f7e7a66817cd6273e3e6b5a72d18177b08b2f/cryptography-45.0.5-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57c816dfbd1659a367831baca4b775b2a5b43c003daf52e9d57e1d30bc2e1b0e", size = 4374079, upload-time = "2025-07-02T13:06:02.043Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c5/c0e07d84a9a2a8a0ed4f865e58f37c71af3eab7d5e094ff1b21f3f3af3bc/cryptography-45.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b9e38e0a83cd51e07f5a48ff9691cae95a79bea28fe4ded168a8e5c6c77e819d", size = 3321362, upload-time = "2025-07-02T13:06:04.463Z" }, + { url = "https://files.pythonhosted.org/packages/c0/71/9bdbcfd58d6ff5084687fe722c58ac718ebedbc98b9f8f93781354e6d286/cryptography-45.0.5-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8c4a6ff8a30e9e3d38ac0539e9a9e02540ab3f827a3394f8852432f6b0ea152e", size = 3587878, upload-time = "2025-07-02T13:06:06.339Z" }, + { url = "https://files.pythonhosted.org/packages/f0/63/83516cfb87f4a8756eaa4203f93b283fda23d210fc14e1e594bd5f20edb6/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6", size = 4152447, upload-time = "2025-07-02T13:06:08.345Z" }, + { url = "https://files.pythonhosted.org/packages/22/11/d2823d2a5a0bd5802b3565437add16f5c8ce1f0778bf3822f89ad2740a38/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18", size = 4386778, upload-time = "2025-07-02T13:06:10.263Z" }, + { url = "https://files.pythonhosted.org/packages/5f/38/6bf177ca6bce4fe14704ab3e93627c5b0ca05242261a2e43ef3168472540/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463", size = 4151627, upload-time = "2025-07-02T13:06:13.097Z" }, + { url = "https://files.pythonhosted.org/packages/38/6a/69fc67e5266bff68a91bcb81dff8fb0aba4d79a78521a08812048913e16f/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1", size = 4385593, upload-time = "2025-07-02T13:06:15.689Z" }, + { url = "https://files.pythonhosted.org/packages/f6/34/31a1604c9a9ade0fdab61eb48570e09a796f4d9836121266447b0eaf7feb/cryptography-45.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e357286c1b76403dd384d938f93c46b2b058ed4dfcdce64a770f0537ed3feb6f", size = 3331106, upload-time = "2025-07-02T13:06:18.058Z" }, +] + +[[package]] +name = "deepseek-tokenizer" +version = "0.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tokenizers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/23/2603b475d4fcf57cee5e7d70341533706860abd44f7fa506f4b052c36ed4/deepseek_tokenizer-0.1.3.tar.gz", hash = "sha256:7fb2c1ea41412af14bbe38ec898cfde688996c6cc3a952867216c62048da78c6", size = 1926836, upload-time = "2025-02-05T14:54:53.755Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/11/34a47500b5c927cf82db648644455ec0e9d309aa65470efb946d64e324be/deepseek_tokenizer-0.1.3-py3-none-any.whl", hash = "sha256:3117e27d129c06778cde9b9e02d90b6c565994735f899b75a8f9b49f88c980a3", size = 1978914, upload-time = "2025-02-05T14:54:45.835Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "eval-type-backport" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/ea/8b0ac4469d4c347c6a385ff09dc3c048c2d021696664e26c7ee6791631b5/eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1", size = 9079, upload-time = "2024-12-21T20:09:46.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/31/55cd413eaccd39125368be33c46de24a1f639f2e12349b0361b4678f3915/eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a", size = 5830, upload-time = "2024-12-21T20:09:44.175Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "fastmcp" +version = "2.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "authlib" }, + { name = "exceptiongroup" }, + { name = "httpx" }, + { name = "mcp" }, + { name = "openapi-pydantic" }, + { name = "python-dotenv" }, + { name = "rich" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/17/d2b5981180717e6e689d0aaa6215d1ddcec6cb065c6f6b45c471ab7d2edb/fastmcp-2.9.2.tar.gz", hash = "sha256:c000eb0a2d50afcc7d26be4c867abf5c995ca0e0119f17417d50f61e2e17347c", size = 2663824, upload-time = "2025-06-26T15:03:15.395Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/60/223e1975e65ed7c6cead0fda947d4cdb4f7ee5ec60a7c8ea3b3787868f67/fastmcp-2.9.2-py3-none-any.whl", hash = "sha256:3626a3d9b1fa6325756273b6d1fe1ec610baafec957ac22f7aa1dc17c1db8b93", size = 162690, upload-time = "2025-06-26T15:03:13.553Z" }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/02/0835e6ab9cfc03916fe3f78c0956cfcdb6ff2669ffa6651065d5ebf7fc98/fsspec-2025.7.0.tar.gz", hash = "sha256:786120687ffa54b8283d942929540d8bc5ccfa820deb555a2b5d0ed2b737bf58", size = 304432, upload-time = "2025-07-15T16:05:21.19Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/e0/014d5d9d7a4564cf1c40b5039bc882db69fd881111e03ab3657ac0b218e2/fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21", size = 199597, upload-time = "2025-07-15T16:05:19.529Z" }, +] + +[[package]] +name = "google-auth" +version = "2.40.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029, upload-time = "2025-06-04T18:04:57.577Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137, upload-time = "2025-06-04T18:04:55.573Z" }, +] + +[[package]] +name = "google-genai" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "google-auth" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/44/64c6c23724580add879cbcca81ffed500955c1c21850468cd4dcf9c62a03/google_genai-1.11.0.tar.gz", hash = "sha256:0643b2f5373fbeae945d0cd5a37d157eab0c172bb5e14e905f2f8d45aa51cabb", size = 160955, upload-time = "2025-04-16T23:34:37.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/55f97203720cbda5a1c8e0460793914980e41c6ca4859fea735dd66d2c3a/google_genai-1.11.0-py3-none-any.whl", hash = "sha256:34fbe3c85419adbcddcb8222f99514596b3a69c80ff1a4ae30a01a763da27acc", size = 159687, upload-time = "2025-04-16T23:34:36.595Z" }, +] + +[[package]] +name = "groq" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/75/3b3e6b9495ca8e2e7f08373412f2f3c5fd0d8cbe987905d7caebc84abba2/groq-0.13.1.tar.gz", hash = "sha256:588fd5bee984f4eb46ec89552778d5698b9e9614435defef868645c19463cbcc", size = 109709, upload-time = "2024-12-15T08:50:28.984Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/ad/2bf9b91085b1d67e432bc3071378570e7fd0326c5c648376e6230d3fed99/groq-0.13.1-py3-none-any.whl", hash = "sha256:0c5d1d6df93de55de705fe73729b79baaa0c871f7575d6aa64b2962b56101b3e", size = 109114, upload-time = "2024-12-15T08:50:26.091Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.1.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/d4/7685999e85945ed0d7f0762b686ae7015035390de1161dcea9d5276c134c/hf_xet-1.1.5.tar.gz", hash = "sha256:69ebbcfd9ec44fdc2af73441619eeb06b94ee34511bbcf57cd423820090f5694", size = 495969, upload-time = "2025-06-20T21:48:38.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/89/a1119eebe2836cb25758e7661d6410d3eae982e2b5e974bcc4d250be9012/hf_xet-1.1.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f52c2fa3635b8c37c7764d8796dfa72706cc4eded19d638331161e82b0792e23", size = 2687929, upload-time = "2025-06-20T21:48:32.284Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/2c78e28f309396e71ec8e4e9304a6483dcbc36172b5cea8f291994163425/hf_xet-1.1.5-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9fa6e3ee5d61912c4a113e0708eaaef987047616465ac7aa30f7121a48fc1af8", size = 2556338, upload-time = "2025-06-20T21:48:30.079Z" }, + { url = "https://files.pythonhosted.org/packages/6d/2f/6cad7b5fe86b7652579346cb7f85156c11761df26435651cbba89376cd2c/hf_xet-1.1.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc874b5c843e642f45fd85cda1ce599e123308ad2901ead23d3510a47ff506d1", size = 3102894, upload-time = "2025-06-20T21:48:28.114Z" }, + { url = "https://files.pythonhosted.org/packages/d0/54/0fcf2b619720a26fbb6cc941e89f2472a522cd963a776c089b189559447f/hf_xet-1.1.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dbba1660e5d810bd0ea77c511a99e9242d920790d0e63c0e4673ed36c4022d18", size = 3002134, upload-time = "2025-06-20T21:48:25.906Z" }, + { url = "https://files.pythonhosted.org/packages/f3/92/1d351ac6cef7c4ba8c85744d37ffbfac2d53d0a6c04d2cabeba614640a78/hf_xet-1.1.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ab34c4c3104133c495785d5d8bba3b1efc99de52c02e759cf711a91fd39d3a14", size = 3171009, upload-time = "2025-06-20T21:48:33.987Z" }, + { url = "https://files.pythonhosted.org/packages/c9/65/4b2ddb0e3e983f2508528eb4501288ae2f84963586fbdfae596836d5e57a/hf_xet-1.1.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:83088ecea236d5113de478acb2339f92c95b4fb0462acaa30621fac02f5a534a", size = 3279245, upload-time = "2025-06-20T21:48:36.051Z" }, + { url = "https://files.pythonhosted.org/packages/f0/55/ef77a85ee443ae05a9e9cba1c9f0dd9241eb42da2aeba1dc50f51154c81a/hf_xet-1.1.5-cp37-abi3-win_amd64.whl", hash = "sha256:73e167d9807d166596b4b2f0b585c6d5bd84a26dea32843665a8b58f6edba245", size = 2738931, upload-time = "2025-06-20T21:48:39.482Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/fa/66bd985dd0b7c109a3bcb89272ee0bfb7e2b4d06309ad7b38ff866734b2a/httpx_sse-0.4.1.tar.gz", hash = "sha256:8f44d34414bc7b21bf3602713005c5df4917884f76072479b21f68befa4ea26e", size = 12998, upload-time = "2025-06-24T13:21:05.71Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/0a/6269e3473b09aed2dab8aa1a600c70f31f00ae1349bee30658f7e358a159/httpx_sse-0.4.1-py3-none-any.whl", hash = "sha256:cba42174344c3a5b06f255ce65b350880f962d99ead85e776f23c6618a377a37", size = 8054, upload-time = "2025-06-24T13:21:04.772Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "0.33.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/9e/9366b7349fc125dd68b9d384a0fea84d67b7497753fe92c71b67e13f47c4/huggingface_hub-0.33.4.tar.gz", hash = "sha256:6af13478deae120e765bfd92adad0ae1aec1ad8c439b46f23058ad5956cbca0a", size = 426674, upload-time = "2025-07-11T12:32:48.694Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/7b/98daa50a2db034cab6cd23a3de04fa2358cb691593d28e9130203eb7a805/huggingface_hub-0.33.4-py3-none-any.whl", hash = "sha256:09f9f4e7ca62547c70f8b82767eefadd2667f4e116acba2e3e62a5a81815a7bb", size = 515339, upload-time = "2025-07-11T12:32:46.346Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "jiter" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759, upload-time = "2025-05-18T19:04:59.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/7e/4011b5c77bec97cb2b572f566220364e3e21b51c48c5bd9c4a9c26b41b67/jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303", size = 317215, upload-time = "2025-05-18T19:03:04.303Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4f/144c1b57c39692efc7ea7d8e247acf28e47d0912800b34d0ad815f6b2824/jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e", size = 322814, upload-time = "2025-05-18T19:03:06.433Z" }, + { url = "https://files.pythonhosted.org/packages/63/1f/db977336d332a9406c0b1f0b82be6f71f72526a806cbb2281baf201d38e3/jiter-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8b3e0068c26ddedc7abc6fac37da2d0af16b921e288a5a613f4b86f050354f", size = 345237, upload-time = "2025-05-18T19:03:07.833Z" }, + { url = "https://files.pythonhosted.org/packages/d7/1c/aa30a4a775e8a672ad7f21532bdbfb269f0706b39c6ff14e1f86bdd9e5ff/jiter-0.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:286299b74cc49e25cd42eea19b72aa82c515d2f2ee12d11392c56d8701f52224", size = 370999, upload-time = "2025-05-18T19:03:09.338Z" }, + { url = "https://files.pythonhosted.org/packages/35/df/f8257abc4207830cb18880781b5f5b716bad5b2a22fb4330cfd357407c5b/jiter-0.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ed5649ceeaeffc28d87fb012d25a4cd356dcd53eff5acff1f0466b831dda2a7", size = 491109, upload-time = "2025-05-18T19:03:11.13Z" }, + { url = "https://files.pythonhosted.org/packages/06/76/9e1516fd7b4278aa13a2cc7f159e56befbea9aa65c71586305e7afa8b0b3/jiter-0.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2ab0051160cb758a70716448908ef14ad476c3774bd03ddce075f3c1f90a3d6", size = 388608, upload-time = "2025-05-18T19:03:12.911Z" }, + { url = "https://files.pythonhosted.org/packages/6d/64/67750672b4354ca20ca18d3d1ccf2c62a072e8a2d452ac3cf8ced73571ef/jiter-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03997d2f37f6b67d2f5c475da4412be584e1cec273c1cfc03d642c46db43f8cf", size = 352454, upload-time = "2025-05-18T19:03:14.741Z" }, + { url = "https://files.pythonhosted.org/packages/96/4d/5c4e36d48f169a54b53a305114be3efa2bbffd33b648cd1478a688f639c1/jiter-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c404a99352d839fed80d6afd6c1d66071f3bacaaa5c4268983fc10f769112e90", size = 391833, upload-time = "2025-05-18T19:03:16.426Z" }, + { url = "https://files.pythonhosted.org/packages/0b/de/ce4a6166a78810bd83763d2fa13f85f73cbd3743a325469a4a9289af6dae/jiter-0.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66e989410b6666d3ddb27a74c7e50d0829704ede652fd4c858e91f8d64b403d0", size = 523646, upload-time = "2025-05-18T19:03:17.704Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a6/3bc9acce53466972964cf4ad85efecb94f9244539ab6da1107f7aed82934/jiter-0.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b532d3af9ef4f6374609a3bcb5e05a1951d3bf6190dc6b176fdb277c9bbf15ee", size = 514735, upload-time = "2025-05-18T19:03:19.44Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d8/243c2ab8426a2a4dea85ba2a2ba43df379ccece2145320dfd4799b9633c5/jiter-0.10.0-cp310-cp310-win32.whl", hash = "sha256:da9be20b333970e28b72edc4dff63d4fec3398e05770fb3205f7fb460eb48dd4", size = 210747, upload-time = "2025-05-18T19:03:21.184Z" }, + { url = "https://files.pythonhosted.org/packages/37/7a/8021bd615ef7788b98fc76ff533eaac846322c170e93cbffa01979197a45/jiter-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:f59e533afed0c5b0ac3eba20d2548c4a550336d8282ee69eb07b37ea526ee4e5", size = 207484, upload-time = "2025-05-18T19:03:23.046Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dd/6cefc6bd68b1c3c979cecfa7029ab582b57690a31cd2f346c4d0ce7951b6/jiter-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3bebe0c558e19902c96e99217e0b8e8b17d570906e72ed8a87170bc290b1e978", size = 317473, upload-time = "2025-05-18T19:03:25.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/cf/fc33f5159ce132be1d8dd57251a1ec7a631c7df4bd11e1cd198308c6ae32/jiter-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:558cc7e44fd8e507a236bee6a02fa17199ba752874400a0ca6cd6e2196cdb7dc", size = 321971, upload-time = "2025-05-18T19:03:27.255Z" }, + { url = "https://files.pythonhosted.org/packages/68/a4/da3f150cf1d51f6c472616fb7650429c7ce053e0c962b41b68557fdf6379/jiter-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d613e4b379a07d7c8453c5712ce7014e86c6ac93d990a0b8e7377e18505e98d", size = 345574, upload-time = "2025-05-18T19:03:28.63Z" }, + { url = "https://files.pythonhosted.org/packages/84/34/6e8d412e60ff06b186040e77da5f83bc158e9735759fcae65b37d681f28b/jiter-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f62cf8ba0618eda841b9bf61797f21c5ebd15a7a1e19daab76e4e4b498d515b2", size = 371028, upload-time = "2025-05-18T19:03:30.292Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d9/9ee86173aae4576c35a2f50ae930d2ccb4c4c236f6cb9353267aa1d626b7/jiter-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:919d139cdfa8ae8945112398511cb7fca58a77382617d279556b344867a37e61", size = 491083, upload-time = "2025-05-18T19:03:31.654Z" }, + { url = "https://files.pythonhosted.org/packages/d9/2c/f955de55e74771493ac9e188b0f731524c6a995dffdcb8c255b89c6fb74b/jiter-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ddbc6ae311175a3b03bd8994881bc4635c923754932918e18da841632349db", size = 388821, upload-time = "2025-05-18T19:03:33.184Z" }, + { url = "https://files.pythonhosted.org/packages/81/5a/0e73541b6edd3f4aada586c24e50626c7815c561a7ba337d6a7eb0a915b4/jiter-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c440ea003ad10927a30521a9062ce10b5479592e8a70da27f21eeb457b4a9c5", size = 352174, upload-time = "2025-05-18T19:03:34.965Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c0/61eeec33b8c75b31cae42be14d44f9e6fe3ac15a4e58010256ac3abf3638/jiter-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc347c87944983481e138dea467c0551080c86b9d21de6ea9306efb12ca8f606", size = 391869, upload-time = "2025-05-18T19:03:36.436Z" }, + { url = "https://files.pythonhosted.org/packages/41/22/5beb5ee4ad4ef7d86f5ea5b4509f680a20706c4a7659e74344777efb7739/jiter-0.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13252b58c1f4d8c5b63ab103c03d909e8e1e7842d302473f482915d95fefd605", size = 523741, upload-time = "2025-05-18T19:03:38.168Z" }, + { url = "https://files.pythonhosted.org/packages/ea/10/768e8818538e5817c637b0df52e54366ec4cebc3346108a4457ea7a98f32/jiter-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d1bbf3c465de4a24ab12fb7766a0003f6f9bce48b8b6a886158c4d569452dc5", size = 514527, upload-time = "2025-05-18T19:03:39.577Z" }, + { url = "https://files.pythonhosted.org/packages/73/6d/29b7c2dc76ce93cbedabfd842fc9096d01a0550c52692dfc33d3cc889815/jiter-0.10.0-cp311-cp311-win32.whl", hash = "sha256:db16e4848b7e826edca4ccdd5b145939758dadf0dc06e7007ad0e9cfb5928ae7", size = 210765, upload-time = "2025-05-18T19:03:41.271Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/d394706deb4c660137caf13e33d05a031d734eb99c051142e039d8ceb794/jiter-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c9c1d5f10e18909e993f9641f12fe1c77b3e9b533ee94ffa970acc14ded3812", size = 209234, upload-time = "2025-05-18T19:03:42.918Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b5/348b3313c58f5fbfb2194eb4d07e46a35748ba6e5b3b3046143f3040bafa/jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b", size = 312262, upload-time = "2025-05-18T19:03:44.637Z" }, + { url = "https://files.pythonhosted.org/packages/9c/4a/6a2397096162b21645162825f058d1709a02965606e537e3304b02742e9b/jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744", size = 320124, upload-time = "2025-05-18T19:03:46.341Z" }, + { url = "https://files.pythonhosted.org/packages/2a/85/1ce02cade7516b726dd88f59a4ee46914bf79d1676d1228ef2002ed2f1c9/jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2", size = 345330, upload-time = "2025-05-18T19:03:47.596Z" }, + { url = "https://files.pythonhosted.org/packages/75/d0/bb6b4f209a77190ce10ea8d7e50bf3725fc16d3372d0a9f11985a2b23eff/jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026", size = 369670, upload-time = "2025-05-18T19:03:49.334Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f5/a61787da9b8847a601e6827fbc42ecb12be2c925ced3252c8ffcb56afcaf/jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c", size = 489057, upload-time = "2025-05-18T19:03:50.66Z" }, + { url = "https://files.pythonhosted.org/packages/12/e4/6f906272810a7b21406c760a53aadbe52e99ee070fc5c0cb191e316de30b/jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959", size = 389372, upload-time = "2025-05-18T19:03:51.98Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ba/77013b0b8ba904bf3762f11e0129b8928bff7f978a81838dfcc958ad5728/jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a", size = 352038, upload-time = "2025-05-18T19:03:53.703Z" }, + { url = "https://files.pythonhosted.org/packages/67/27/c62568e3ccb03368dbcc44a1ef3a423cb86778a4389e995125d3d1aaa0a4/jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95", size = 391538, upload-time = "2025-05-18T19:03:55.046Z" }, + { url = "https://files.pythonhosted.org/packages/c0/72/0d6b7e31fc17a8fdce76164884edef0698ba556b8eb0af9546ae1a06b91d/jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea", size = 523557, upload-time = "2025-05-18T19:03:56.386Z" }, + { url = "https://files.pythonhosted.org/packages/2f/09/bc1661fbbcbeb6244bd2904ff3a06f340aa77a2b94e5a7373fd165960ea3/jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b", size = 514202, upload-time = "2025-05-18T19:03:57.675Z" }, + { url = "https://files.pythonhosted.org/packages/1b/84/5a5d5400e9d4d54b8004c9673bbe4403928a00d28529ff35b19e9d176b19/jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01", size = 211781, upload-time = "2025-05-18T19:03:59.025Z" }, + { url = "https://files.pythonhosted.org/packages/9b/52/7ec47455e26f2d6e5f2ea4951a0652c06e5b995c291f723973ae9e724a65/jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49", size = 206176, upload-time = "2025-05-18T19:04:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b0/279597e7a270e8d22623fea6c5d4eeac328e7d95c236ed51a2b884c54f70/jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644", size = 311617, upload-time = "2025-05-18T19:04:02.078Z" }, + { url = "https://files.pythonhosted.org/packages/91/e3/0916334936f356d605f54cc164af4060e3e7094364add445a3bc79335d46/jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a", size = 318947, upload-time = "2025-05-18T19:04:03.347Z" }, + { url = "https://files.pythonhosted.org/packages/6a/8e/fd94e8c02d0e94539b7d669a7ebbd2776e51f329bb2c84d4385e8063a2ad/jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6", size = 344618, upload-time = "2025-05-18T19:04:04.709Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b0/f9f0a2ec42c6e9c2e61c327824687f1e2415b767e1089c1d9135f43816bd/jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3", size = 368829, upload-time = "2025-05-18T19:04:06.912Z" }, + { url = "https://files.pythonhosted.org/packages/e8/57/5bbcd5331910595ad53b9fd0c610392ac68692176f05ae48d6ce5c852967/jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2", size = 491034, upload-time = "2025-05-18T19:04:08.222Z" }, + { url = "https://files.pythonhosted.org/packages/9b/be/c393df00e6e6e9e623a73551774449f2f23b6ec6a502a3297aeeece2c65a/jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25", size = 388529, upload-time = "2025-05-18T19:04:09.566Z" }, + { url = "https://files.pythonhosted.org/packages/42/3e/df2235c54d365434c7f150b986a6e35f41ebdc2f95acea3036d99613025d/jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041", size = 350671, upload-time = "2025-05-18T19:04:10.98Z" }, + { url = "https://files.pythonhosted.org/packages/c6/77/71b0b24cbcc28f55ab4dbfe029f9a5b73aeadaba677843fc6dc9ed2b1d0a/jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca", size = 390864, upload-time = "2025-05-18T19:04:12.722Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d3/ef774b6969b9b6178e1d1e7a89a3bd37d241f3d3ec5f8deb37bbd203714a/jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4", size = 522989, upload-time = "2025-05-18T19:04:14.261Z" }, + { url = "https://files.pythonhosted.org/packages/0c/41/9becdb1d8dd5d854142f45a9d71949ed7e87a8e312b0bede2de849388cb9/jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e", size = 513495, upload-time = "2025-05-18T19:04:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/9c/36/3468e5a18238bdedae7c4d19461265b5e9b8e288d3f86cd89d00cbb48686/jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d", size = 211289, upload-time = "2025-05-18T19:04:17.541Z" }, + { url = "https://files.pythonhosted.org/packages/7e/07/1c96b623128bcb913706e294adb5f768fb7baf8db5e1338ce7b4ee8c78ef/jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4", size = 205074, upload-time = "2025-05-18T19:04:19.21Z" }, + { url = "https://files.pythonhosted.org/packages/54/46/caa2c1342655f57d8f0f2519774c6d67132205909c65e9aa8255e1d7b4f4/jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca", size = 318225, upload-time = "2025-05-18T19:04:20.583Z" }, + { url = "https://files.pythonhosted.org/packages/43/84/c7d44c75767e18946219ba2d703a5a32ab37b0bc21886a97bc6062e4da42/jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070", size = 350235, upload-time = "2025-05-18T19:04:22.363Z" }, + { url = "https://files.pythonhosted.org/packages/01/16/f5a0135ccd968b480daad0e6ab34b0c7c5ba3bc447e5088152696140dcb3/jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca", size = 207278, upload-time = "2025-05-18T19:04:23.627Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9b/1d646da42c3de6c2188fdaa15bce8ecb22b635904fc68be025e21249ba44/jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522", size = 310866, upload-time = "2025-05-18T19:04:24.891Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0e/26538b158e8a7c7987e94e7aeb2999e2e82b1f9d2e1f6e9874ddf71ebda0/jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8", size = 318772, upload-time = "2025-05-18T19:04:26.161Z" }, + { url = "https://files.pythonhosted.org/packages/7b/fb/d302893151caa1c2636d6574d213e4b34e31fd077af6050a9c5cbb42f6fb/jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216", size = 344534, upload-time = "2025-05-18T19:04:27.495Z" }, + { url = "https://files.pythonhosted.org/packages/01/d8/5780b64a149d74e347c5128d82176eb1e3241b1391ac07935693466d6219/jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4", size = 369087, upload-time = "2025-05-18T19:04:28.896Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5b/f235a1437445160e777544f3ade57544daf96ba7e96c1a5b24a6f7ac7004/jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426", size = 490694, upload-time = "2025-05-18T19:04:30.183Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/9c3d4617caa2ff89cf61b41e83820c27ebb3f7b5fae8a72901e8cd6ff9be/jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12", size = 388992, upload-time = "2025-05-18T19:04:32.028Z" }, + { url = "https://files.pythonhosted.org/packages/68/b1/344fd14049ba5c94526540af7eb661871f9c54d5f5601ff41a959b9a0bbd/jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9", size = 351723, upload-time = "2025-05-18T19:04:33.467Z" }, + { url = "https://files.pythonhosted.org/packages/41/89/4c0e345041186f82a31aee7b9d4219a910df672b9fef26f129f0cda07a29/jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a", size = 392215, upload-time = "2025-05-18T19:04:34.827Z" }, + { url = "https://files.pythonhosted.org/packages/55/58/ee607863e18d3f895feb802154a2177d7e823a7103f000df182e0f718b38/jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853", size = 522762, upload-time = "2025-05-18T19:04:36.19Z" }, + { url = "https://files.pythonhosted.org/packages/15/d0/9123fb41825490d16929e73c212de9a42913d68324a8ce3c8476cae7ac9d/jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86", size = 513427, upload-time = "2025-05-18T19:04:37.544Z" }, + { url = "https://files.pythonhosted.org/packages/d8/b3/2bd02071c5a2430d0b70403a34411fc519c2f227da7b03da9ba6a956f931/jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357", size = 210127, upload-time = "2025-05-18T19:04:38.837Z" }, + { url = "https://files.pythonhosted.org/packages/03/0c/5fe86614ea050c3ecd728ab4035534387cd41e7c1855ef6c031f1ca93e3f/jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00", size = 318527, upload-time = "2025-05-18T19:04:40.612Z" }, + { url = "https://files.pythonhosted.org/packages/b3/4a/4175a563579e884192ba6e81725fc0448b042024419be8d83aa8a80a3f44/jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5", size = 354213, upload-time = "2025-05-18T19:04:41.894Z" }, +] + +[[package]] +name = "json-repair" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/9c/5ef83a13541c3444e0b949e88b3aa0f4e364e37acf4ffa9de476d36a3de0/json_repair-0.35.0.tar.gz", hash = "sha256:e70f834865a4ae5fe64352c23c1c16d3b70c5dd62dc544a169d8b0932bdbdcaa", size = 29053, upload-time = "2024-12-31T12:03:52.239Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/c7/9a63ff79f2f6350c0f5e504bad940386015381b4d171bd73077465a7dbbc/json_repair-0.35.0-py3-none-any.whl", hash = "sha256:1d429407158474d28a996e745b8f8f7dc78957cb2cfbc92120b9f580b5230a9e", size = 19908, upload-time = "2024-12-31T12:03:51.234Z" }, +] + +[[package]] +name = "loguru" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "win32-setctime", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "mcp" +version = "1.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/f2/dc2450e566eeccf92d89a00c3e813234ad58e2ba1e31d11467a09ac4f3b9/mcp-1.9.4.tar.gz", hash = "sha256:cfb0bcd1a9535b42edaef89947b9e18a8feb49362e1cc059d6e7fc636f2cb09f", size = 333294, upload-time = "2025-06-12T08:20:30.158Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/fc/80e655c955137393c443842ffcc4feccab5b12fa7cb8de9ced90f90e6998/mcp-1.9.4-py3-none-any.whl", hash = "sha256:7fcf36b62936adb8e63f89346bccca1268eeca9bf6dfb562ee10b1dfbda9dac0", size = 130232, upload-time = "2025-06-12T08:20:28.551Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mistralai" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eval-type-backport" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/d0/11e0116a02aa88701422ccc048185ed8834754f3b94140bfad09620c9d11/mistralai-1.7.0.tar.gz", hash = "sha256:94e3eb23c1d3ed398a95352062fd8c92993cc3754ed18e9a35b60aa3db0bd103", size = 141981, upload-time = "2025-04-16T19:42:56.703Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/77/eb7519ddfccb6428ac430129e7b42cc662e710cb719f82c0ffe79ab50859/mistralai-1.7.0-py3-none-any.whl", hash = "sha256:e0e75ab8508598d69ae19b14d9d7e905db6259a2de3cf9204946a27e9bf81c5d", size = 301483, upload-time = "2025-04-16T19:42:55.434Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/1b/1d565e0f6e156e1522ab564176b8b29d71e13d8caf003a08768df3d5cec5/numpy-2.2.0.tar.gz", hash = "sha256:140dd80ff8981a583a60980be1a655068f8adebf7a45a06a6858c873fcdcd4a0", size = 20225497, upload-time = "2024-12-08T15:45:53.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/81/3882353e097204fe4d7a5fe026b694b0104b78f930c969faadeed1538e00/numpy-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1e25507d85da11ff5066269d0bd25d06e0a0f2e908415534f3e603d2a78e4ffa", size = 21212476, upload-time = "2024-12-08T15:20:47.292Z" }, + { url = "https://files.pythonhosted.org/packages/2c/64/5577dc71240272749e07fcacb47c0f29e31ba4fbd1613fefbd1aa88efc29/numpy-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a62eb442011776e4036af5c8b1a00b706c5bc02dc15eb5344b0c750428c94219", size = 14351441, upload-time = "2024-12-08T15:21:10.966Z" }, + { url = "https://files.pythonhosted.org/packages/c9/43/850c040481c19c1c2289203a606df1a202eeb3aa81440624bac891024f83/numpy-2.2.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:b606b1aaf802e6468c2608c65ff7ece53eae1a6874b3765f69b8ceb20c5fa78e", size = 5390304, upload-time = "2024-12-08T15:21:20.431Z" }, + { url = "https://files.pythonhosted.org/packages/73/96/a4c8a86300dbafc7e4f44d8986f8b64950b7f4640a2dc5c91e036afe28c6/numpy-2.2.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:36b2b43146f646642b425dd2027730f99bac962618ec2052932157e213a040e9", size = 6925476, upload-time = "2024-12-08T15:21:31.436Z" }, + { url = "https://files.pythonhosted.org/packages/0c/0a/22129c3107c4fb237f97876df4399a5c3a83f3d95f86e0353ae6fbbd202f/numpy-2.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fe8f3583e0607ad4e43a954e35c1748b553bfe9fdac8635c02058023277d1b3", size = 14329997, upload-time = "2024-12-08T15:21:53.67Z" }, + { url = "https://files.pythonhosted.org/packages/4c/49/c2adeccc8a47bcd9335ec000dfcb4de34a7c34aeaa23af57cd504017e8c3/numpy-2.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:122fd2fcfafdefc889c64ad99c228d5a1f9692c3a83f56c292618a59aa60ae83", size = 16378908, upload-time = "2024-12-08T15:22:18.524Z" }, + { url = "https://files.pythonhosted.org/packages/8d/85/b65f4596748cc5468c0a978a16b3be45f6bcec78339b0fe7bce71d121d89/numpy-2.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3f2f5cddeaa4424a0a118924b988746db6ffa8565e5829b1841a8a3bd73eb59a", size = 15540949, upload-time = "2024-12-08T15:22:42.538Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b3/3b18321c94a6a6a1d972baf1b39a6de50e65c991002c014ffbcce7e09be8/numpy-2.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fe4bb0695fe986a9e4deec3b6857003b4cfe5c5e4aac0b95f6a658c14635e31", size = 18167677, upload-time = "2024-12-08T15:23:11.062Z" }, + { url = "https://files.pythonhosted.org/packages/41/f0/fa2a76e893a05764e4474f6011575c4e4ccf32af9c95bfcc8ef4b8a99f69/numpy-2.2.0-cp310-cp310-win32.whl", hash = "sha256:b30042fe92dbd79f1ba7f6898fada10bdaad1847c44f2dff9a16147e00a93661", size = 6570288, upload-time = "2024-12-08T15:23:22.863Z" }, + { url = "https://files.pythonhosted.org/packages/97/4e/0b7debcd013214db224997b0d3e39bb7b3656d37d06dfc31bb57d42d143b/numpy-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dc1d6d66f8d37843ed281773c7174f03bf7ad826523f73435deb88ba60d2d4", size = 12912730, upload-time = "2024-12-08T15:23:42.938Z" }, + { url = "https://files.pythonhosted.org/packages/80/1b/736023977a96e787c4e7653a1ac2d31d4f6ab6b4048f83c8359f7c0af2e3/numpy-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9874bc2ff574c40ab7a5cbb7464bf9b045d617e36754a7bc93f933d52bd9ffc6", size = 21216607, upload-time = "2024-12-08T15:24:13.737Z" }, + { url = "https://files.pythonhosted.org/packages/85/4f/5f0be4c5c93525e663573bab9e29bd88a71f85de3a0d01413ee05bce0c2f/numpy-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0da8495970f6b101ddd0c38ace92edea30e7e12b9a926b57f5fabb1ecc25bb90", size = 14387756, upload-time = "2024-12-08T15:24:35.67Z" }, + { url = "https://files.pythonhosted.org/packages/36/78/c38af7833c4f29999cdacdf12452b43b660cd25a1990ea9a7edf1fb01f17/numpy-2.2.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0557eebc699c1c34cccdd8c3778c9294e8196df27d713706895edc6f57d29608", size = 5388483, upload-time = "2024-12-08T15:24:45.64Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b5/306ac6ee3f8f0c51abd3664ee8a9b8e264cbf179a860674827151ecc0a9c/numpy-2.2.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:3579eaeb5e07f3ded59298ce22b65f877a86ba8e9fe701f5576c99bb17c283da", size = 6929721, upload-time = "2024-12-08T15:24:57.76Z" }, + { url = "https://files.pythonhosted.org/packages/ea/15/e33a7d86d8ce91de82c34ce94a87f2b8df891e603675e83ec7039325ff10/numpy-2.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40deb10198bbaa531509aad0cd2f9fadb26c8b94070831e2208e7df543562b74", size = 14334667, upload-time = "2024-12-08T15:25:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/52/33/10825f580f42a353f744abc450dcd2a4b1e6f1931abb0ccbd1d63bd3993c/numpy-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2aed8fcf8abc3020d6a9ccb31dbc9e7d7819c56a348cc88fd44be269b37427e", size = 16390204, upload-time = "2024-12-08T15:25:45.414Z" }, + { url = "https://files.pythonhosted.org/packages/b4/24/36cce77559572bdc6c8bcdd2f3e0db03c7079d14b9a1cd342476d7f451e8/numpy-2.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a222d764352c773aa5ebde02dd84dba3279c81c6db2e482d62a3fa54e5ece69b", size = 15556123, upload-time = "2024-12-08T15:26:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/05/51/2d706d14adee8f5c70c5de3831673d4d57051fc9ac6f3f6bff8811d2f9bd/numpy-2.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4e58666988605e251d42c2818c7d3d8991555381be26399303053b58a5bbf30d", size = 18179898, upload-time = "2024-12-08T15:26:37.996Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e7/ea8b7652564113f218e75b296e3545a256d88b233021f792fd08591e8f33/numpy-2.2.0-cp311-cp311-win32.whl", hash = "sha256:4723a50e1523e1de4fccd1b9a6dcea750c2102461e9a02b2ac55ffeae09a4410", size = 6568146, upload-time = "2024-12-08T15:26:50.015Z" }, + { url = "https://files.pythonhosted.org/packages/d0/06/3d1ff6ed377cb0340baf90487a35f15f9dc1db8e0a07de2bf2c54a8e490f/numpy-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:16757cf28621e43e252c560d25b15f18a2f11da94fea344bf26c599b9cf54b73", size = 12916677, upload-time = "2024-12-08T15:28:41.652Z" }, + { url = "https://files.pythonhosted.org/packages/7f/bc/a20dc4e1d051149052762e7647455311865d11c603170c476d1e910a353e/numpy-2.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cff210198bb4cae3f3c100444c5eaa573a823f05c253e7188e1362a5555235b3", size = 20909153, upload-time = "2024-12-08T15:29:15.013Z" }, + { url = "https://files.pythonhosted.org/packages/60/3d/ac4fb63f36db94f4c7db05b45e3ecb3f88f778ca71850664460c78cfde41/numpy-2.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b92a5828bd4d9aa0952492b7de803135038de47343b2aa3cc23f3b71a3dc4e", size = 14095021, upload-time = "2024-12-08T15:29:42.037Z" }, + { url = "https://files.pythonhosted.org/packages/41/6d/a654d519d24e4fcc7a83d4a51209cda086f26cf30722b3d8ffc1aa9b775e/numpy-2.2.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ebe5e59545401fbb1b24da76f006ab19734ae71e703cdb4a8b347e84a0cece67", size = 5125491, upload-time = "2024-12-08T15:29:52.979Z" }, + { url = "https://files.pythonhosted.org/packages/e6/22/fab7e1510a62e5092f4e6507a279020052b89f11d9cfe52af7f52c243b04/numpy-2.2.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e2b8cd48a9942ed3f85b95ca4105c45758438c7ed28fff1e4ce3e57c3b589d8e", size = 6658534, upload-time = "2024-12-08T15:30:06.424Z" }, + { url = "https://files.pythonhosted.org/packages/fc/29/a3d938ddc5a534cd53df7ab79d20a68db8c67578de1df0ae0118230f5f54/numpy-2.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57fcc997ffc0bef234b8875a54d4058afa92b0b0c4223fc1f62f24b3b5e86038", size = 14046306, upload-time = "2024-12-08T15:30:31.079Z" }, + { url = "https://files.pythonhosted.org/packages/90/24/d0bbb56abdd8934f30384632e3c2ca1ebfeb5d17e150c6e366ba291de36b/numpy-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ad7d11b309bd132d74397fcf2920933c9d1dc865487128f5c03d580f2c3d03", size = 16095819, upload-time = "2024-12-08T15:31:00.056Z" }, + { url = "https://files.pythonhosted.org/packages/99/9c/58a673faa9e8a0e77248e782f7a17410cf7259b326265646fd50ed49c4e1/numpy-2.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cb24cca1968b21355cc6f3da1a20cd1cebd8a023e3c5b09b432444617949085a", size = 15243215, upload-time = "2024-12-08T15:31:26.698Z" }, + { url = "https://files.pythonhosted.org/packages/9c/61/f311693f78cbf635cfb69ce9e1e857ff83937a27d93c96ac5932fd33e330/numpy-2.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0798b138c291d792f8ea40fe3768610f3c7dd2574389e37c3f26573757c8f7ef", size = 17860175, upload-time = "2024-12-08T15:31:57.807Z" }, + { url = "https://files.pythonhosted.org/packages/11/3e/491c34262cb1fc9dd13a00beb80d755ee0517b17db20e54cac7aa524533e/numpy-2.2.0-cp312-cp312-win32.whl", hash = "sha256:afe8fb968743d40435c3827632fd36c5fbde633b0423da7692e426529b1759b1", size = 6273281, upload-time = "2024-12-08T15:32:11.897Z" }, + { url = "https://files.pythonhosted.org/packages/89/ea/00537f599eb230771157bc509f6ea5b2dddf05d4b09f9d2f1d7096a18781/numpy-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:3a4199f519e57d517ebd48cb76b36c82da0360781c6a0353e64c0cac30ecaad3", size = 12613227, upload-time = "2024-12-08T15:32:34.792Z" }, + { url = "https://files.pythonhosted.org/packages/bd/4c/0d1eef206545c994289e7a9de21b642880a11e0ed47a2b0c407c688c4f69/numpy-2.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f8c8b141ef9699ae777c6278b52c706b653bf15d135d302754f6b2e90eb30367", size = 20895707, upload-time = "2024-12-08T15:33:12.723Z" }, + { url = "https://files.pythonhosted.org/packages/16/cb/88f6c1e6df83002c421d5f854ccf134aa088aa997af786a5dac3f32ec99b/numpy-2.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f0986e917aca18f7a567b812ef7ca9391288e2acb7a4308aa9d265bd724bdae", size = 14110592, upload-time = "2024-12-08T15:33:38.416Z" }, + { url = "https://files.pythonhosted.org/packages/b4/54/817e6894168a43f33dca74199ba0dd0f1acd99aa6323ed6d323d63d640a2/numpy-2.2.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:1c92113619f7b272838b8d6702a7f8ebe5edea0df48166c47929611d0b4dea69", size = 5110858, upload-time = "2024-12-08T15:33:48.779Z" }, + { url = "https://files.pythonhosted.org/packages/c7/99/00d8a1a8eb70425bba7880257ed73fed08d3e8d05da4202fb6b9a81d5ee4/numpy-2.2.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5a145e956b374e72ad1dff82779177d4a3c62bc8248f41b80cb5122e68f22d13", size = 6645143, upload-time = "2024-12-08T15:34:02.815Z" }, + { url = "https://files.pythonhosted.org/packages/34/86/5b9c2b7c56e7a9d9297a0a4be0b8433f498eba52a8f5892d9132b0f64627/numpy-2.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18142b497d70a34b01642b9feabb70156311b326fdddd875a9981f34a369b671", size = 14042812, upload-time = "2024-12-08T15:34:26.323Z" }, + { url = "https://files.pythonhosted.org/packages/df/54/13535f74391dbe5f479ceed96f1403267be302c840040700d4fd66688089/numpy-2.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7d41d1612c1a82b64697e894b75db6758d4f21c3ec069d841e60ebe54b5b571", size = 16093419, upload-time = "2024-12-08T15:34:53.056Z" }, + { url = "https://files.pythonhosted.org/packages/dd/37/dfb2056842ac61315f225aa56f455da369f5223e4c5a38b91d20da1b628b/numpy-2.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a98f6f20465e7618c83252c02041517bd2f7ea29be5378f09667a8f654a5918d", size = 15238969, upload-time = "2024-12-08T15:35:20.37Z" }, + { url = "https://files.pythonhosted.org/packages/5a/3d/d20d24ee313992f0b7e7b9d9eef642d9b545d39d5b91c4a2cc8c98776328/numpy-2.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e09d40edfdb4e260cb1567d8ae770ccf3b8b7e9f0d9b5c2a9992696b30ce2742", size = 17855705, upload-time = "2024-12-08T15:35:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/5b/40/944c9ee264f875a2db6f79380944fd2b5bb9d712bb4a134d11f45ad5b693/numpy-2.2.0-cp313-cp313-win32.whl", hash = "sha256:3905a5fffcc23e597ee4d9fb3fcd209bd658c352657548db7316e810ca80458e", size = 6270078, upload-time = "2024-12-08T15:39:19.519Z" }, + { url = "https://files.pythonhosted.org/packages/30/04/e1ee6f8b22034302d4c5c24e15782bdedf76d90b90f3874ed0b48525def0/numpy-2.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a184288538e6ad699cbe6b24859206e38ce5fba28f3bcfa51c90d0502c1582b2", size = 12605791, upload-time = "2024-12-08T15:39:38.513Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fb/51d458625cd6134d60ac15180ae50995d7d21b0f2f92a6286ae7b0792d19/numpy-2.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7832f9e8eb00be32f15fdfb9a981d6955ea9adc8574c521d48710171b6c55e95", size = 20920160, upload-time = "2024-12-08T15:36:18.605Z" }, + { url = "https://files.pythonhosted.org/packages/b4/34/162ae0c5d2536ea4be98c813b5161c980f0443cd5765fde16ddfe3450140/numpy-2.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0dd071b95bbca244f4cb7f70b77d2ff3aaaba7fa16dc41f58d14854a6204e6c", size = 14119064, upload-time = "2024-12-08T15:36:40.875Z" }, + { url = "https://files.pythonhosted.org/packages/17/6c/4195dd0e1c41c55f466d516e17e9e28510f32af76d23061ea3da67438e3c/numpy-2.2.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b0b227dcff8cdc3efbce66d4e50891f04d0a387cce282fe1e66199146a6a8fca", size = 5152778, upload-time = "2024-12-08T15:36:50.563Z" }, + { url = "https://files.pythonhosted.org/packages/2f/47/ea804ae525832c8d05ed85b560dfd242d34e4bb0962bc269ccaa720fb934/numpy-2.2.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ab153263a7c5ccaf6dfe7e53447b74f77789f28ecb278c3b5d49db7ece10d6d", size = 6667605, upload-time = "2024-12-08T15:37:01.343Z" }, + { url = "https://files.pythonhosted.org/packages/76/99/34d20e50b3d894bb16b5374bfbee399ab8ff3a33bf1e1f0b8acfe7bbd70d/numpy-2.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e500aba968a48e9019e42c0c199b7ec0696a97fa69037bea163b55398e390529", size = 14013275, upload-time = "2024-12-08T15:37:22.411Z" }, + { url = "https://files.pythonhosted.org/packages/69/8f/a1df7bd02d434ab82539517d1b98028985700cfc4300bc5496fb140ca648/numpy-2.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:440cfb3db4c5029775803794f8638fbdbf71ec702caf32735f53b008e1eaece3", size = 16074900, upload-time = "2024-12-08T15:37:47.078Z" }, + { url = "https://files.pythonhosted.org/packages/04/94/b419e7a76bf21a00fcb03c613583f10e389fdc8dfe420412ff5710c8ad3d/numpy-2.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a55dc7a7f0b6198b07ec0cd445fbb98b05234e8b00c5ac4874a63372ba98d4ab", size = 15219122, upload-time = "2024-12-08T15:38:10.437Z" }, + { url = "https://files.pythonhosted.org/packages/65/d9/dddf398b2b6c5d750892a207a469c2854a8db0f033edaf72103af8cf05aa/numpy-2.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4bddbaa30d78c86329b26bd6aaaea06b1e47444da99eddac7bf1e2fab717bd72", size = 17851668, upload-time = "2024-12-08T15:38:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/d4/dc/09a4e5819a9782a213c0eb4eecacdc1cd75ad8dac99279b04cfccb7eeb0a/numpy-2.2.0-cp313-cp313t-win32.whl", hash = "sha256:30bf971c12e4365153afb31fc73f441d4da157153f3400b82db32d04de1e4066", size = 6325288, upload-time = "2024-12-08T15:38:48.456Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e1/e0d06ec34036c92b43aef206efe99a5f5f04e12c776eab82a36e00c40afc/numpy-2.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d35717333b39d1b6bb8433fa758a55f1081543de527171543a2b710551d40881", size = 12692303, upload-time = "2024-12-08T15:39:08.17Z" }, + { url = "https://files.pythonhosted.org/packages/f3/18/6d4e1274f221073058b621f4df8050958b7564b24b4fa25be9f1b7639274/numpy-2.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e12c6c1ce84628c52d6367863773f7c8c8241be554e8b79686e91a43f1733773", size = 21043901, upload-time = "2024-12-08T15:40:09.263Z" }, + { url = "https://files.pythonhosted.org/packages/19/3e/2b20599e7ead7ae1b89a77bb34f88c5ec12e43fbb320576ed646388d2cb7/numpy-2.2.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:b6207dc8fb3c8cb5668e885cef9ec7f70189bec4e276f0ff70d5aa078d32c88e", size = 6789122, upload-time = "2024-12-08T15:40:21.876Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5a/378954132c192fafa6c3d5c160092a427c7562e5bda0cc6ad9cc37008a7a/numpy-2.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a50aeff71d0f97b6450d33940c7181b08be1441c6c193e678211bff11aa725e7", size = 16194018, upload-time = "2024-12-08T15:40:45.485Z" }, + { url = "https://files.pythonhosted.org/packages/67/17/209bda34fc83f3436834392f44643e66dcf3c77465f232102e7f1c7d8eae/numpy-2.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:df12a1f99b99f569a7c2ae59aa2d31724e8d835fc7f33e14f4792e3071d11221", size = 12819486, upload-time = "2024-12-08T15:41:05.529Z" }, +] + +[[package]] +name = "openai" +version = "1.75.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/b1/318f5d4c482f19c5fcbcde190801bfaaaec23413cda0b88a29f6897448ff/openai-1.75.0.tar.gz", hash = "sha256:fb3ea907efbdb1bcfd0c44507ad9c961afd7dce3147292b54505ecfd17be8fd1", size = 429492, upload-time = "2025-04-16T16:49:29.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/9a/f34f163294345f123673ed03e77c33dee2534f3ac1f9d18120384457304d/openai-1.75.0-py3-none-any.whl", hash = "sha256:fe6f932d2ded3b429ff67cc9ad118c71327db32eb9d32dd723de3acfca337125", size = 646972, upload-time = "2025-04-16T16:49:27.196Z" }, +] + +[[package]] +name = "openapi-pydantic" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/2e/58d83848dd1a79cb92ed8e63f6ba901ca282c5f09d04af9423ec26c56fd7/openapi_pydantic-0.5.1.tar.gz", hash = "sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d", size = 60892, upload-time = "2025-01-08T19:29:27.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381, upload-time = "2025-01-08T19:29:25.275Z" }, +] + +[[package]] +name = "orjson" +version = "3.10.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/0b/8c7eaf1e2152f1e0fb28ae7b22e2b35a6b1992953a1ebe0371ba4d41d3ad/orjson-3.10.13.tar.gz", hash = "sha256:eb9bfb14ab8f68d9d9492d4817ae497788a15fd7da72e14dfabc289c3bb088ec", size = 5438389, upload-time = "2024-12-29T23:43:04.03Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/c4/67206a3cd1b677e2dc8d0de102bebc993ce083548542461e9fa397ce3e7c/orjson-3.10.13-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1232c5e873a4d1638ef957c5564b4b0d6f2a6ab9e207a9b3de9de05a09d1d920", size = 248733, upload-time = "2024-12-29T23:40:30.477Z" }, + { url = "https://files.pythonhosted.org/packages/9f/c7/49202bcefb75c614d8f221845dd185d4e4dab1aace9a09e99a840dd22abb/orjson-3.10.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d26a0eca3035619fa366cbaf49af704c7cb1d4a0e6c79eced9f6a3f2437964b6", size = 136954, upload-time = "2024-12-29T23:40:33.142Z" }, + { url = "https://files.pythonhosted.org/packages/87/6c/21518e60589c27cc4bc76156d1a0980fe2be7f5419f5269e800e2e5902bb/orjson-3.10.13-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d4b6acd7c9c829895e50d385a357d4b8c3fafc19c5989da2bae11783b0fd4977", size = 149101, upload-time = "2024-12-29T23:40:35.931Z" }, + { url = "https://files.pythonhosted.org/packages/e3/88/5eac5856b28df0273ac07187cd20a0e6108799d9f5f3382e2dd1398ec1b3/orjson-3.10.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1884e53c6818686891cc6fc5a3a2540f2f35e8c76eac8dc3b40480fb59660b00", size = 140445, upload-time = "2024-12-29T23:40:38.671Z" }, + { url = "https://files.pythonhosted.org/packages/a9/66/a6455588709b6d0cb4ebc95bc775c19c548d1d1e354bd10ad018123698a2/orjson-3.10.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a428afb5720f12892f64920acd2eeb4d996595bf168a26dd9190115dbf1130d", size = 156532, upload-time = "2024-12-29T23:40:39.98Z" }, + { url = "https://files.pythonhosted.org/packages/c2/41/58f73d6656f1c9d6e736549f36066ce16ba91e33a639c8cca278af09baf3/orjson-3.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba5b13b8739ce5b630c65cb1c85aedbd257bcc2b9c256b06ab2605209af75a2e", size = 131261, upload-time = "2024-12-29T23:40:42.629Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7e/81ca17c438733741265e8ebfa3e5436aa4e61332f91ebdc11eff27c7b152/orjson-3.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cab83e67f6aabda1b45882254b2598b48b80ecc112968fc6483fa6dae609e9f0", size = 139822, upload-time = "2024-12-29T23:40:45.157Z" }, + { url = "https://files.pythonhosted.org/packages/be/fc/b1d72a5f431fc5ae9edfa5bb41fb3b5e9532a4181c5268e67bc2717217bf/orjson-3.10.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:62c3cc00c7e776c71c6b7b9c48c5d2701d4c04e7d1d7cdee3572998ee6dc57cc", size = 131901, upload-time = "2024-12-29T23:40:47.752Z" }, + { url = "https://files.pythonhosted.org/packages/31/f6/8cdcd06e0d4ee37eba1c7a6cd2c5a8798a3a533f9b17b5e48a2a7dcdf6c9/orjson-3.10.13-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:dc03db4922e75bbc870b03fc49734cefbd50fe975e0878327d200022210b82d8", size = 415733, upload-time = "2024-12-29T23:40:49.022Z" }, + { url = "https://files.pythonhosted.org/packages/f1/37/0aec8417b5a18136651d57af7955a5991a80abca6356cd4dd04a869ee8e6/orjson-3.10.13-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:22f1c9a30b43d14a041a6ea190d9eca8a6b80c4beb0e8b67602c82d30d6eec3e", size = 142454, upload-time = "2024-12-29T23:40:51.867Z" }, + { url = "https://files.pythonhosted.org/packages/b7/06/679318d8da3ce897b1d0518073abe6b762e7994b4f765b959b39a7d909a4/orjson-3.10.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b42f56821c29e697c68d7d421410d7c1d8f064ae288b525af6a50cf99a4b1200", size = 130672, upload-time = "2024-12-29T23:40:55.862Z" }, + { url = "https://files.pythonhosted.org/packages/90/e4/3d0018b3aee93385393b37af000214b18c6873bb0d0097ba1355b7cb23d2/orjson-3.10.13-cp310-cp310-win32.whl", hash = "sha256:0dbf3b97e52e093d7c3e93eb5eb5b31dc7535b33c2ad56872c83f0160f943487", size = 143675, upload-time = "2024-12-29T23:40:57.11Z" }, + { url = "https://files.pythonhosted.org/packages/30/f1/3608a164a4fea07b795ace71862375e2c1686537d8f907d4c9f6f1d63008/orjson-3.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:46c249b4e934453be4ff2e518cd1adcd90467da7391c7a79eaf2fbb79c51e8c7", size = 135084, upload-time = "2024-12-29T23:40:58.468Z" }, + { url = "https://files.pythonhosted.org/packages/01/44/7a047e47779953e3f657a612ad36f71a0bca02cf57ff490c427e22b01833/orjson-3.10.13-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a36c0d48d2f084c800763473020a12976996f1109e2fcb66cfea442fdf88047f", size = 248732, upload-time = "2024-12-29T23:41:01.073Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e9/54976977aaacc5030fdd8012479638bb8d4e2a16519b516ac2bd03a48eab/orjson-3.10.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0065896f85d9497990731dfd4a9991a45b0a524baec42ef0a63c34630ee26fd6", size = 136954, upload-time = "2024-12-29T23:41:02.561Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a7/663fb04e031d5c80a348aeb7271c6042d13f80393c4951b8801a703b89c0/orjson-3.10.13-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92b4ec30d6025a9dcdfe0df77063cbce238c08d0404471ed7a79f309364a3d19", size = 149101, upload-time = "2024-12-29T23:41:04.692Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f1/5f2a4bf7525ef4acf48902d2df2bcc1c5aa38f6cc17ee0729a1d3e110ddb/orjson-3.10.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a94542d12271c30044dadad1125ee060e7a2048b6c7034e432e116077e1d13d2", size = 140445, upload-time = "2024-12-29T23:41:06.151Z" }, + { url = "https://files.pythonhosted.org/packages/12/d3/e68afa1db9860880e59260348b54c0518d8dfe2297e932f8e333ace878fa/orjson-3.10.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3723e137772639af8adb68230f2aa4bcb27c48b3335b1b1e2d49328fed5e244c", size = 156530, upload-time = "2024-12-29T23:41:07.444Z" }, + { url = "https://files.pythonhosted.org/packages/77/ee/492b198c77b9985ae28e0c6b8092c2994cd18d6be40dc7cb7f9a385b7096/orjson-3.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f00c7fb18843bad2ac42dc1ce6dd214a083c53f1e324a0fd1c8137c6436269b", size = 131260, upload-time = "2024-12-29T23:41:08.784Z" }, + { url = "https://files.pythonhosted.org/packages/57/d2/5167cc1ccbe56bacdd9fc79e6a3276cba6aa90057305e8485db58b8250c4/orjson-3.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0e2759d3172300b2f892dee85500b22fca5ac49e0c42cfff101aaf9c12ac9617", size = 139821, upload-time = "2024-12-29T23:41:10.042Z" }, + { url = "https://files.pythonhosted.org/packages/74/f0/c1cf568e0f90d812e00c77da2db04a13e94afe639665b9a09c271456dc41/orjson-3.10.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee948c6c01f6b337589c88f8e0bb11e78d32a15848b8b53d3f3b6fea48842c12", size = 131904, upload-time = "2024-12-29T23:41:12.877Z" }, + { url = "https://files.pythonhosted.org/packages/55/7d/a611542afbbacca4693a2319744944134df62957a1f206303d5b3160e349/orjson-3.10.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:aa6fe68f0981fba0d4bf9cdc666d297a7cdba0f1b380dcd075a9a3dd5649a69e", size = 415733, upload-time = "2024-12-29T23:41:15.69Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/e8182716695cd8d5ebec49d283645b8c7b1de7ed1c27db2891b6957e71f6/orjson-3.10.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dbcd7aad6bcff258f6896abfbc177d54d9b18149c4c561114f47ebfe74ae6bfd", size = 142456, upload-time = "2024-12-29T23:41:18.433Z" }, + { url = "https://files.pythonhosted.org/packages/dc/10/e4b40f15be7e4e991737d77062399c7f67da9b7e3bc28bbcb25de1717df3/orjson-3.10.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2149e2fcd084c3fd584881c7f9d7f9e5ad1e2e006609d8b80649655e0d52cd02", size = 130676, upload-time = "2024-12-29T23:41:19.747Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b1/8b9fb36d470fe8ff99727972c77846673ebc962cb09a5af578804f9f2408/orjson-3.10.13-cp311-cp311-win32.whl", hash = "sha256:89367767ed27b33c25c026696507c76e3d01958406f51d3a2239fe9e91959df2", size = 143672, upload-time = "2024-12-29T23:41:22.476Z" }, + { url = "https://files.pythonhosted.org/packages/b5/15/90b3711f40d27aff80dd42c1eec2f0ed704a1fa47eef7120350e2797892d/orjson-3.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:dca1d20f1af0daff511f6e26a27354a424f0b5cf00e04280279316df0f604a6f", size = 135082, upload-time = "2024-12-29T23:41:24.032Z" }, + { url = "https://files.pythonhosted.org/packages/35/84/adf8842cf36904e6200acff76156862d48d39705054c1e7c5fa98fe14417/orjson-3.10.13-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a3614b00621c77f3f6487792238f9ed1dd8a42f2ec0e6540ee34c2d4e6db813a", size = 248778, upload-time = "2024-12-29T23:41:25.423Z" }, + { url = "https://files.pythonhosted.org/packages/69/2f/22ac0c5f46748e9810287a5abaeabdd67f1120a74140db7d529582c92342/orjson-3.10.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c976bad3996aa027cd3aef78aa57873f3c959b6c38719de9724b71bdc7bd14b", size = 136759, upload-time = "2024-12-29T23:41:28.153Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/6f05de77dd383cb623e2807bceae13f136e9f179cd32633b7a27454e953f/orjson-3.10.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f74d878d1efb97a930b8a9f9898890067707d683eb5c7e20730030ecb3fb930", size = 149123, upload-time = "2024-12-29T23:41:30.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5c/b5e144e9adbb1dc7d1fdf54af9510756d09b65081806f905d300a926a755/orjson-3.10.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33ef84f7e9513fb13b3999c2a64b9ca9c8143f3da9722fbf9c9ce51ce0d8076e", size = 140557, upload-time = "2024-12-29T23:41:33.319Z" }, + { url = "https://files.pythonhosted.org/packages/91/fd/7bdbc0aa374d49cdb917ee51c80851c99889494be81d5e7ec9f5f9cbe149/orjson-3.10.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd2bcde107221bb9c2fa0c4aaba735a537225104173d7e19cf73f70b3126c993", size = 156626, upload-time = "2024-12-29T23:41:34.638Z" }, + { url = "https://files.pythonhosted.org/packages/48/90/e583d6e29937ec30a164f1d86a0439c1a2477b5aae9f55d94b37a4f5b5f0/orjson-3.10.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:064b9dbb0217fd64a8d016a8929f2fae6f3312d55ab3036b00b1d17399ab2f3e", size = 131551, upload-time = "2024-12-29T23:41:36.023Z" }, + { url = "https://files.pythonhosted.org/packages/47/0b/838c00ec7f048527aa0382299cd178bbe07c2cb1024b3111883e85d56d1f/orjson-3.10.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0044b0b8c85a565e7c3ce0a72acc5d35cda60793edf871ed94711e712cb637d", size = 139790, upload-time = "2024-12-29T23:41:37.366Z" }, + { url = "https://files.pythonhosted.org/packages/ac/90/df06ac390f319a61d55a7a4efacb5d7082859f6ea33f0fdd5181ad0dde0c/orjson-3.10.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7184f608ad563032e398f311910bc536e62b9fbdca2041be889afcbc39500de8", size = 131717, upload-time = "2024-12-29T23:41:38.684Z" }, + { url = "https://files.pythonhosted.org/packages/ea/68/eafb5e2fc84aafccfbd0e9e0552ff297ef5f9b23c7f2600cc374095a50de/orjson-3.10.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d36f689e7e1b9b6fb39dbdebc16a6f07cbe994d3644fb1c22953020fc575935f", size = 415690, upload-time = "2024-12-29T23:41:42.418Z" }, + { url = "https://files.pythonhosted.org/packages/b8/cf/aa93b48801b2e42da223ef5a99b3e4970b02e7abea8509dd2a6a083e27fa/orjson-3.10.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54433e421618cd5873e51c0e9d0b9fb35f7bf76eb31c8eab20b3595bb713cd3d", size = 142396, upload-time = "2024-12-29T23:41:43.877Z" }, + { url = "https://files.pythonhosted.org/packages/8b/50/fb1a7060b79231c60a688037c2c8e9fe289b5a4378ec1f32cf8d33d9adf8/orjson-3.10.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e1ba0c5857dd743438acecc1cd0e1adf83f0a81fee558e32b2b36f89e40cee8b", size = 130842, upload-time = "2024-12-29T23:41:45.367Z" }, + { url = "https://files.pythonhosted.org/packages/94/e6/44067052e28a13176da874ca53419b43cf0f6f01f4bf0539f2f70d8eacf6/orjson-3.10.13-cp312-cp312-win32.whl", hash = "sha256:a42b9fe4b0114b51eb5cdf9887d8c94447bc59df6dbb9c5884434eab947888d8", size = 143773, upload-time = "2024-12-29T23:41:48.048Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7d/510939d1b7f8ba387849e83666e898f214f38baa46c5efde94561453974d/orjson-3.10.13-cp312-cp312-win_amd64.whl", hash = "sha256:3a7df63076435f39ec024bdfeb4c9767ebe7b49abc4949068d61cf4857fa6d6c", size = 135234, upload-time = "2024-12-29T23:41:49.332Z" }, + { url = "https://files.pythonhosted.org/packages/ef/42/482fced9a135c798f31e1088f608fa16735fdc484eb8ffdd29aa32d4e842/orjson-3.10.13-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2cdaf8b028a976ebab837a2c27b82810f7fc76ed9fb243755ba650cc83d07730", size = 248726, upload-time = "2024-12-29T23:41:51.984Z" }, + { url = "https://files.pythonhosted.org/packages/00/e7/6345653906ee6d2d6eabb767cdc4482c7809572dbda59224f40e48931efa/orjson-3.10.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48a946796e390cbb803e069472de37f192b7a80f4ac82e16d6eb9909d9e39d56", size = 126032, upload-time = "2024-12-29T23:41:53.577Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b8/0d2a2c739458ff7f9917a132225365d72d18f4b65c50cb8ebb5afb6fe184/orjson-3.10.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d64f1db5ecbc21eb83097e5236d6ab7e86092c1cd4c216c02533332951afc", size = 131547, upload-time = "2024-12-29T23:41:56.334Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ac/a1dc389cf364d576cf587a6f78dac6c905c5cac31b9dbd063bbb24335bf7/orjson-3.10.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:711878da48f89df194edd2ba603ad42e7afed74abcd2bac164685e7ec15f96de", size = 131682, upload-time = "2024-12-29T23:41:57.702Z" }, + { url = "https://files.pythonhosted.org/packages/43/6c/debab76b830aba6449ec8a75ac77edebb0e7decff63eb3ecfb2cf6340a2e/orjson-3.10.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:cf16f06cb77ce8baf844bc222dbcb03838f61d0abda2c3341400c2b7604e436e", size = 415621, upload-time = "2024-12-29T23:41:59.21Z" }, + { url = "https://files.pythonhosted.org/packages/c2/32/106e605db5369a6717036065e2b41ac52bd0d2712962edb3e026a452dc07/orjson-3.10.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8257c3fb8dd7b0b446b5e87bf85a28e4071ac50f8c04b6ce2d38cb4abd7dff57", size = 142388, upload-time = "2024-12-29T23:42:01.978Z" }, + { url = "https://files.pythonhosted.org/packages/a3/02/6b2103898d60c2565bf97abffdf3a4cf338920b9feb55eec1fd791ab10ee/orjson-3.10.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9c3a87abe6f849a4a7ac8a8a1dede6320a4303d5304006b90da7a3cd2b70d2c", size = 130825, upload-time = "2024-12-29T23:42:03.432Z" }, + { url = "https://files.pythonhosted.org/packages/87/7c/db115e2380435da569732999d5c4c9b9868efe72e063493cb73c36bb649a/orjson-3.10.13-cp313-cp313-win32.whl", hash = "sha256:527afb6ddb0fa3fe02f5d9fba4920d9d95da58917826a9be93e0242da8abe94a", size = 143723, upload-time = "2024-12-29T23:42:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/cc/5e/c2b74a0b38ec561a322d8946663924556c1f967df2eefe1b9e0b98a33950/orjson-3.10.13-cp313-cp313-win_amd64.whl", hash = "sha256:b5f7c298d4b935b222f52d6c7f2ba5eafb59d690d9a3840b7b5c5cda97f6ec5c", size = 134968, upload-time = "2024-12-29T23:42:08.104Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "plotly" +version = "5.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tenacity" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/4f/428f6d959818d7425a94c190a6b26fbc58035cbef40bf249be0b62a9aedd/plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae", size = 9479398, upload-time = "2024-09-12T15:36:31.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/ae/580600f441f6fc05218bd6c9d5794f4aef072a7d9093b291f1c50a9db8bc/plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089", size = 19054220, upload-time = "2024-09-12T15:36:24.08Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087, upload-time = "2025-01-20T15:55:35.072Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816, upload-time = "2025-01-20T15:55:29.98Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, +] + +[[package]] +name = "pydantic" +version = "2.10.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/0f/27908242621b14e649a84e62b133de45f84c255eecb350ab02979844a788/pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9", size = 786486, upload-time = "2024-12-03T15:59:02.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/51/72c18c55cf2f46ff4f91ebcc8f75aa30f7305f3d726be3f4ebffb4ae972b/pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d", size = 456997, upload-time = "2024-12-03T15:58:59.867Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.27.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/9f/7de1f19b6aea45aeb441838782d68352e71bfa98ee6fa048d5041991b33e/pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235", size = 412785, upload-time = "2024-11-22T00:24:49.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/ce/60fd96895c09738648c83f3f00f595c807cb6735c70d3306b548cc96dd49/pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a", size = 1897984, upload-time = "2024-11-22T00:21:25.431Z" }, + { url = "https://files.pythonhosted.org/packages/fd/b9/84623d6b6be98cc209b06687d9bca5a7b966ffed008d15225dd0d20cce2e/pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b", size = 1807491, upload-time = "2024-11-22T00:21:27.318Z" }, + { url = "https://files.pythonhosted.org/packages/01/72/59a70165eabbc93b1111d42df9ca016a4aa109409db04304829377947028/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278", size = 1831953, upload-time = "2024-11-22T00:21:28.606Z" }, + { url = "https://files.pythonhosted.org/packages/7c/0c/24841136476adafd26f94b45bb718a78cb0500bd7b4f8d667b67c29d7b0d/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05", size = 1856071, upload-time = "2024-11-22T00:21:29.931Z" }, + { url = "https://files.pythonhosted.org/packages/53/5e/c32957a09cceb2af10d7642df45d1e3dbd8596061f700eac93b801de53c0/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4", size = 2038439, upload-time = "2024-11-22T00:21:32.245Z" }, + { url = "https://files.pythonhosted.org/packages/e4/8f/979ab3eccd118b638cd6d8f980fea8794f45018255a36044dea40fe579d4/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f", size = 2787416, upload-time = "2024-11-22T00:21:33.708Z" }, + { url = "https://files.pythonhosted.org/packages/02/1d/00f2e4626565b3b6d3690dab4d4fe1a26edd6a20e53749eb21ca892ef2df/pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08", size = 2134548, upload-time = "2024-11-22T00:21:35.823Z" }, + { url = "https://files.pythonhosted.org/packages/9d/46/3112621204128b90898adc2e721a3cd6cf5626504178d6f32c33b5a43b79/pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6", size = 1989882, upload-time = "2024-11-22T00:21:37.872Z" }, + { url = "https://files.pythonhosted.org/packages/49/ec/557dd4ff5287ffffdf16a31d08d723de6762bb1b691879dc4423392309bc/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807", size = 1995829, upload-time = "2024-11-22T00:21:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b2/610dbeb74d8d43921a7234555e4c091cb050a2bdb8cfea86d07791ce01c5/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c", size = 2091257, upload-time = "2024-11-22T00:21:41.99Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7f/4bf8e9d26a9118521c80b229291fa9558a07cdd9a968ec2d5c1026f14fbc/pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206", size = 2143894, upload-time = "2024-11-22T00:21:44.193Z" }, + { url = "https://files.pythonhosted.org/packages/1f/1c/875ac7139c958f4390f23656fe696d1acc8edf45fb81e4831960f12cd6e4/pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c", size = 1816081, upload-time = "2024-11-22T00:21:45.468Z" }, + { url = "https://files.pythonhosted.org/packages/d7/41/55a117acaeda25ceae51030b518032934f251b1dac3704a53781383e3491/pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17", size = 1981109, upload-time = "2024-11-22T00:21:47.452Z" }, + { url = "https://files.pythonhosted.org/packages/27/39/46fe47f2ad4746b478ba89c561cafe4428e02b3573df882334bd2964f9cb/pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8", size = 1895553, upload-time = "2024-11-22T00:21:48.859Z" }, + { url = "https://files.pythonhosted.org/packages/1c/00/0804e84a78b7fdb394fff4c4f429815a10e5e0993e6ae0e0b27dd20379ee/pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330", size = 1807220, upload-time = "2024-11-22T00:21:50.354Z" }, + { url = "https://files.pythonhosted.org/packages/01/de/df51b3bac9820d38371f5a261020f505025df732ce566c2a2e7970b84c8c/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52", size = 1829727, upload-time = "2024-11-22T00:21:51.722Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d9/c01d19da8f9e9fbdb2bf99f8358d145a312590374d0dc9dd8dbe484a9cde/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4", size = 1854282, upload-time = "2024-11-22T00:21:53.098Z" }, + { url = "https://files.pythonhosted.org/packages/5f/84/7db66eb12a0dc88c006abd6f3cbbf4232d26adfd827a28638c540d8f871d/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c", size = 2037437, upload-time = "2024-11-22T00:21:55.185Z" }, + { url = "https://files.pythonhosted.org/packages/34/ac/a2537958db8299fbabed81167d58cc1506049dba4163433524e06a7d9f4c/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de", size = 2780899, upload-time = "2024-11-22T00:21:56.633Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c1/3e38cd777ef832c4fdce11d204592e135ddeedb6c6f525478a53d1c7d3e5/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025", size = 2135022, upload-time = "2024-11-22T00:21:59.154Z" }, + { url = "https://files.pythonhosted.org/packages/7a/69/b9952829f80fd555fe04340539d90e000a146f2a003d3fcd1e7077c06c71/pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e", size = 1987969, upload-time = "2024-11-22T00:22:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/72/257b5824d7988af43460c4e22b63932ed651fe98804cc2793068de7ec554/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919", size = 1994625, upload-time = "2024-11-22T00:22:03.447Z" }, + { url = "https://files.pythonhosted.org/packages/73/c3/78ed6b7f3278a36589bcdd01243189ade7fc9b26852844938b4d7693895b/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c", size = 2090089, upload-time = "2024-11-22T00:22:04.941Z" }, + { url = "https://files.pythonhosted.org/packages/8d/c8/b4139b2f78579960353c4cd987e035108c93a78371bb19ba0dc1ac3b3220/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc", size = 2142496, upload-time = "2024-11-22T00:22:06.57Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f8/171a03e97eb36c0b51981efe0f78460554a1d8311773d3d30e20c005164e/pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9", size = 1811758, upload-time = "2024-11-22T00:22:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fe/4e0e63c418c1c76e33974a05266e5633e879d4061f9533b1706a86f77d5b/pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5", size = 1980864, upload-time = "2024-11-22T00:22:10Z" }, + { url = "https://files.pythonhosted.org/packages/50/fc/93f7238a514c155a8ec02fc7ac6376177d449848115e4519b853820436c5/pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89", size = 1864327, upload-time = "2024-11-22T00:22:11.478Z" }, + { url = "https://files.pythonhosted.org/packages/be/51/2e9b3788feb2aebff2aa9dfbf060ec739b38c05c46847601134cc1fed2ea/pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f", size = 1895239, upload-time = "2024-11-22T00:22:13.775Z" }, + { url = "https://files.pythonhosted.org/packages/7b/9e/f8063952e4a7d0127f5d1181addef9377505dcce3be224263b25c4f0bfd9/pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02", size = 1805070, upload-time = "2024-11-22T00:22:15.438Z" }, + { url = "https://files.pythonhosted.org/packages/2c/9d/e1d6c4561d262b52e41b17a7ef8301e2ba80b61e32e94520271029feb5d8/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c", size = 1828096, upload-time = "2024-11-22T00:22:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/be/65/80ff46de4266560baa4332ae3181fffc4488ea7d37282da1a62d10ab89a4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac", size = 1857708, upload-time = "2024-11-22T00:22:19.412Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ca/3370074ad758b04d9562b12ecdb088597f4d9d13893a48a583fb47682cdf/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb", size = 2037751, upload-time = "2024-11-22T00:22:20.979Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/4ab72d93367194317b99d051947c071aef6e3eb95f7553eaa4208ecf9ba4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529", size = 2733863, upload-time = "2024-11-22T00:22:22.951Z" }, + { url = "https://files.pythonhosted.org/packages/8a/c6/8ae0831bf77f356bb73127ce5a95fe115b10f820ea480abbd72d3cc7ccf3/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35", size = 2161161, upload-time = "2024-11-22T00:22:24.785Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f4/b2fe73241da2429400fc27ddeaa43e35562f96cf5b67499b2de52b528cad/pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089", size = 1993294, upload-time = "2024-11-22T00:22:27.076Z" }, + { url = "https://files.pythonhosted.org/packages/77/29/4bb008823a7f4cc05828198153f9753b3bd4c104d93b8e0b1bfe4e187540/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381", size = 2001468, upload-time = "2024-11-22T00:22:29.346Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a9/0eaceeba41b9fad851a4107e0cf999a34ae8f0d0d1f829e2574f3d8897b0/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb", size = 2091413, upload-time = "2024-11-22T00:22:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/d8/36/eb8697729725bc610fd73940f0d860d791dc2ad557faaefcbb3edbd2b349/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae", size = 2154735, upload-time = "2024-11-22T00:22:32.616Z" }, + { url = "https://files.pythonhosted.org/packages/52/e5/4f0fbd5c5995cc70d3afed1b5c754055bb67908f55b5cb8000f7112749bf/pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c", size = 1833633, upload-time = "2024-11-22T00:22:35.027Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f2/c61486eee27cae5ac781305658779b4a6b45f9cc9d02c90cb21b940e82cc/pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16", size = 1986973, upload-time = "2024-11-22T00:22:37.502Z" }, + { url = "https://files.pythonhosted.org/packages/df/a6/e3f12ff25f250b02f7c51be89a294689d175ac76e1096c32bf278f29ca1e/pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e", size = 1883215, upload-time = "2024-11-22T00:22:39.186Z" }, + { url = "https://files.pythonhosted.org/packages/0f/d6/91cb99a3c59d7b072bded9959fbeab0a9613d5a4935773c0801f1764c156/pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073", size = 1895033, upload-time = "2024-11-22T00:22:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/d35033f81a28b27dedcade9e967e8a40981a765795c9ebae2045bcef05d3/pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08", size = 1807542, upload-time = "2024-11-22T00:22:43.341Z" }, + { url = "https://files.pythonhosted.org/packages/41/c2/491b59e222ec7e72236e512108ecad532c7f4391a14e971c963f624f7569/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf", size = 1827854, upload-time = "2024-11-22T00:22:44.96Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f3/363652651779113189cefdbbb619b7b07b7a67ebb6840325117cc8cc3460/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737", size = 1857389, upload-time = "2024-11-22T00:22:47.305Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/be804aed6b479af5a945daec7538d8bf358d668bdadde4c7888a2506bdfb/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2", size = 2037934, upload-time = "2024-11-22T00:22:49.093Z" }, + { url = "https://files.pythonhosted.org/packages/42/01/295f0bd4abf58902917e342ddfe5f76cf66ffabfc57c2e23c7681a1a1197/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107", size = 2735176, upload-time = "2024-11-22T00:22:50.822Z" }, + { url = "https://files.pythonhosted.org/packages/9d/a0/cd8e9c940ead89cc37812a1a9f310fef59ba2f0b22b4e417d84ab09fa970/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51", size = 2160720, upload-time = "2024-11-22T00:22:52.638Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/9d0980e286627e0aeca4c352a60bd760331622c12d576e5ea4441ac7e15e/pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a", size = 1992972, upload-time = "2024-11-22T00:22:54.31Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ba/ae4480bc0292d54b85cfb954e9d6bd226982949f8316338677d56541b85f/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc", size = 2001477, upload-time = "2024-11-22T00:22:56.451Z" }, + { url = "https://files.pythonhosted.org/packages/55/b7/e26adf48c2f943092ce54ae14c3c08d0d221ad34ce80b18a50de8ed2cba8/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960", size = 2091186, upload-time = "2024-11-22T00:22:58.226Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cc/8491fff5b608b3862eb36e7d29d36a1af1c945463ca4c5040bf46cc73f40/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23", size = 2154429, upload-time = "2024-11-22T00:22:59.985Z" }, + { url = "https://files.pythonhosted.org/packages/78/d8/c080592d80edd3441ab7f88f865f51dae94a157fc64283c680e9f32cf6da/pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05", size = 1833713, upload-time = "2024-11-22T00:23:01.715Z" }, + { url = "https://files.pythonhosted.org/packages/83/84/5ab82a9ee2538ac95a66e51f6838d6aba6e0a03a42aa185ad2fe404a4e8f/pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337", size = 1987897, upload-time = "2024-11-22T00:23:03.497Z" }, + { url = "https://files.pythonhosted.org/packages/df/c3/b15fb833926d91d982fde29c0624c9f225da743c7af801dace0d4e187e71/pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5", size = 1882983, upload-time = "2024-11-22T00:23:05.983Z" }, + { url = "https://files.pythonhosted.org/packages/7c/60/e5eb2d462595ba1f622edbe7b1d19531e510c05c405f0b87c80c1e89d5b1/pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6", size = 1894016, upload-time = "2024-11-22T00:24:03.815Z" }, + { url = "https://files.pythonhosted.org/packages/61/20/da7059855225038c1c4326a840908cc7ca72c7198cb6addb8b92ec81c1d6/pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676", size = 1771648, upload-time = "2024-11-22T00:24:05.981Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fc/5485cf0b0bb38da31d1d292160a4d123b5977841ddc1122c671a30b76cfd/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d", size = 1826929, upload-time = "2024-11-22T00:24:08.163Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ff/fb1284a210e13a5f34c639efc54d51da136074ffbe25ec0c279cf9fbb1c4/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c", size = 1980591, upload-time = "2024-11-22T00:24:10.291Z" }, + { url = "https://files.pythonhosted.org/packages/f1/14/77c1887a182d05af74f6aeac7b740da3a74155d3093ccc7ee10b900cc6b5/pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27", size = 1981326, upload-time = "2024-11-22T00:24:13.169Z" }, + { url = "https://files.pythonhosted.org/packages/06/aa/6f1b2747f811a9c66b5ef39d7f02fbb200479784c75e98290d70004b1253/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f", size = 1989205, upload-time = "2024-11-22T00:24:16.049Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d2/8ce2b074d6835f3c88d85f6d8a399790043e9fdb3d0e43455e72d19df8cc/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed", size = 2079616, upload-time = "2024-11-22T00:24:19.099Z" }, + { url = "https://files.pythonhosted.org/packages/65/71/af01033d4e58484c3db1e5d13e751ba5e3d6b87cc3368533df4c50932c8b/pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f", size = 2133265, upload-time = "2024-11-22T00:24:21.397Z" }, + { url = "https://files.pythonhosted.org/packages/33/72/f881b5e18fbb67cf2fb4ab253660de3c6899dbb2dba409d0b757e3559e3d/pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c", size = 2001864, upload-time = "2024-11-22T00:24:24.354Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, +] + +[[package]] +name = "pygit2" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/4a/72a5f3572912d93d8096f8447a20fe3aff5b5dc65aca08a2083eae54d148/pygit2-1.18.0.tar.gz", hash = "sha256:fbd01d04a4d2ce289aaa02cf858043679bf0dd1f9855c6b88ed95382c1f5011a", size = 773270, upload-time = "2025-04-24T19:07:37.273Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/ca/bc7081416916c1f10b4e4f1a723d39c3a468a9a3cd452e8756066624efff/pygit2-1.18.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2c5606a90c246a90490f30fc4192cf6077391cbef0e7417f690edf964663cf52", size = 5472795, upload-time = "2025-04-24T18:39:24.326Z" }, + { url = "https://files.pythonhosted.org/packages/db/f3/f7b1430b6cb934d65b61490f364494a33e1097e4b6d990a2f362ac46731d/pygit2-1.18.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:7f9c8c8a659c5038d36b520b48a346291116506c0f2563e9e1a194680ce51969", size = 5699127, upload-time = "2025-04-24T18:39:26.209Z" }, + { url = "https://files.pythonhosted.org/packages/7d/eb/8e0ec08a89852d2cf93a148a4d71e2801c754dee6a469376ce91fd4dfb1c/pygit2-1.18.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3cd2304bb1e297b07330929bfbfeb983df75852177809a111cf38dbeec37cbb7", size = 4582145, upload-time = "2025-04-24T18:39:27.621Z" }, + { url = "https://files.pythonhosted.org/packages/0a/21/16add43e95498e6fd6f724b3bbc82450210e7c35c7a7aafc2c616f2b3d88/pygit2-1.18.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:3c156b368fc390f5c0a34b5e8d7709a5dd8a373dea9cab3648df749aad28f517", size = 5436893, upload-time = "2025-04-24T18:39:29.383Z" }, + { url = "https://files.pythonhosted.org/packages/72/8f/42b5d277d1b9075b5b1d269bdc4ca97663aa4dccc1248eb12832311b4797/pygit2-1.18.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:55a5ed47475be125246a384d1125979dce5309cc03da6be6e8687c7de51cca6a", size = 5403541, upload-time = "2025-04-24T18:39:31.334Z" }, + { url = "https://files.pythonhosted.org/packages/0d/64/d697b82d5eeb05d7bd94b4832a8f7f53aa54f83df19e448bab12ae18b29f/pygit2-1.18.0-cp310-cp310-win32.whl", hash = "sha256:f148a9361607357c3679c1315a41dc7413e0ac6709d6f632af0b4a09ce556a31", size = 1220845, upload-time = "2025-04-24T18:16:58.479Z" }, + { url = "https://files.pythonhosted.org/packages/e8/bb/70e2d5f666a9648241cc5c4b7ac3822fc6823ae59e3f328bb456fba4220b/pygit2-1.18.0-cp310-cp310-win_amd64.whl", hash = "sha256:330f5fb6c167682574b59d865baee6e02c0f435ab9dc16bdc6e520c6da3f19f4", size = 1306422, upload-time = "2025-04-24T18:21:40.644Z" }, + { url = "https://files.pythonhosted.org/packages/b8/77/975eda7d22726ccdba7d662bb131f9f0a20fa8e4c2b2f2287351a0d4e64a/pygit2-1.18.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:004e52507715d1ed682b52f20b2ea1571cad5502f2ba0b546e257f4c00c94475", size = 5472783, upload-time = "2025-04-24T18:39:33.792Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5f/ce8eaba091da881457bdc583f3f7a13e92969c045e9f5e6405cc5b7ed8f6/pygit2-1.18.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:502e75607ca269907ccb20582be8279f22d362f39e25a1dd710e75e934a9a095", size = 5705787, upload-time = "2025-04-24T18:39:35.343Z" }, + { url = "https://files.pythonhosted.org/packages/a6/18/1c3cffca973b1723099ffb7ef8288ff547de224c1009d7ff5223fdbd4204/pygit2-1.18.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:3bbf11fa63e8eaf161b89bf6e6cc20cf06b337f779a04d79a4999751b9b15adf", size = 4588495, upload-time = "2025-04-24T18:39:37.282Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f8/24626c55bab0b01b45ba5975d769b1d93db165db79bda2257ff9c5c42d36/pygit2-1.18.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1d97b89ac0a8dddf86727594448bffc78235bcfaee8e5cfd6f410fc1557412b1", size = 5443380, upload-time = "2025-04-24T18:39:39.161Z" }, + { url = "https://files.pythonhosted.org/packages/93/fd/0813becd27708dc8c822936ce27543715f70c83fbc6fc78e5fd5765b3523/pygit2-1.18.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cadfaad3e9856f453b90dd6bc7385d32b9e4393c82e58a3946014c7e95c71913", size = 5409635, upload-time = "2025-04-24T18:39:40.652Z" }, + { url = "https://files.pythonhosted.org/packages/ee/32/e509f102c41d64aa992efcb0b6a4c219ceda7a57760ac80d1e9f3eb7e837/pygit2-1.18.0-cp311-cp311-win32.whl", hash = "sha256:b0e203ec1641140f803e23e5aba61eec9c60cddddaeea4b16f3d29e9def34c9d", size = 1220845, upload-time = "2025-04-24T18:31:31.093Z" }, + { url = "https://files.pythonhosted.org/packages/38/a4/a44bb68f87c138e9799dd02809540b53b41932dc954cbda0a707dc7e404b/pygit2-1.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:c3493b128c0a90e120d82666a934c18e0a27e8485493825534832c14d07a8ed7", size = 1306605, upload-time = "2025-04-24T18:26:45.951Z" }, + { url = "https://files.pythonhosted.org/packages/c5/2a/e62f4a52f44a41f9e325d36c00abb16d28b39b9c905c5825b010c4abdfe2/pygit2-1.18.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bba669496d8ba10de8418ba39357a31ae9e2542aa4ecaa26c5c93ee65eee800a", size = 5468163, upload-time = "2025-04-24T18:39:42.13Z" }, + { url = "https://files.pythonhosted.org/packages/85/d2/01669d6fd909c59448131ae761e1912ab04730e1af775e6d4ee2f9e2b113/pygit2-1.18.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:82a120b2ca7276ffcca971e7c4377235ba393f0a37eeda7fec50195d8381ea6b", size = 5706038, upload-time = "2025-04-24T18:39:44.217Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6b/04422e8e9341d71b2d01b7f57a71ed86aed45c40050c8cf549377fd21ce2/pygit2-1.18.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:8f9fd97dbf30f2e102f50887aec95ab361ebf9193d5e5ae1fda50eb4f4aa80fe", size = 4587465, upload-time = "2025-04-24T18:39:45.659Z" }, + { url = "https://files.pythonhosted.org/packages/34/99/feb31da1ea52864598d57b84c419a1cddd77b46250015b553d31bc5615f7/pygit2-1.18.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d05f5b25758699ccd773723e85ded77c5ffed7f7756d200b0ba26e83b13c58e8", size = 5447363, upload-time = "2025-04-24T18:39:47.16Z" }, + { url = "https://files.pythonhosted.org/packages/32/3f/17a6078975e5ec76514736486528ab4a40c0f3ae1da8142fff8e81d436b3/pygit2-1.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a3f1a951ccfa9f7d55b3be315a8cce982f61a5df0a4874da3ea0988e1e2afad6", size = 5414398, upload-time = "2025-04-24T18:39:48.882Z" }, + { url = "https://files.pythonhosted.org/packages/39/0f/dbaf8cdbadaf161fe0bb9d3d9a7821cc5fc8e1b32281c240412725c55280/pygit2-1.18.0-cp312-cp312-win32.whl", hash = "sha256:547cdec865827f593097d4fda25c46512ad2a933230c23c9c188e9f9e633849f", size = 1221708, upload-time = "2025-04-24T18:36:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/2d46e10d2297d414d03f16e0734eec813c6b5a3f97ea5b70eb1be01b687b/pygit2-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:b5ef2813f9856d0c8d24e2c414481d29296598fa3e02494174a2d7df16ac276a", size = 1306950, upload-time = "2025-04-24T18:41:07.448Z" }, + { url = "https://files.pythonhosted.org/packages/08/9a/0d1c31847fbbb5da2e1d32a215582e063f12f65f727c48f5be554a0693fc/pygit2-1.18.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:42d7d1bccba61d2c3c4539c7f84a8754d287d2fdd55c247e700b582320b9daff", size = 5468137, upload-time = "2025-04-24T18:39:50.345Z" }, + { url = "https://files.pythonhosted.org/packages/42/7d/f0d98d31943bc551341972a4e91a3272c1503e2a9d744f88f2478197182e/pygit2-1.18.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a8af8725a22f85bb580a500f60bd898e1cc6c58576db9400b63507a4ed4526e4", size = 5707866, upload-time = "2025-04-24T18:39:53.139Z" }, + { url = "https://files.pythonhosted.org/packages/c8/85/60b20462d829a61a5ea0822977e94ca433baa5af08a600496477377e6ce3/pygit2-1.18.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:ec71b158f5a4262e01bbcbfb32b0c6f2cb7bce19df84e5a4fb33f54fccb95900", size = 4589400, upload-time = "2025-04-24T18:39:55.089Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b4/8256588c2866fd90dc7f210dca04509f21e6cea17f3b9be1f09d7120ddd0/pygit2-1.18.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:202f6e3e5dadb40c4355b87051bd47e1c18b64bee1b55bd90287115d4cd0eef4", size = 5449088, upload-time = "2025-04-24T18:39:56.695Z" }, + { url = "https://files.pythonhosted.org/packages/39/27/0e062308c183d2875658c7e079b6e054578fac4543849ba4fa878b7227bc/pygit2-1.18.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c59ca7545a6fe38a75ca333ba6b4c6eb32c489d6b2228cd7edab312b0fd7f6d", size = 5416468, upload-time = "2025-04-24T18:39:58.166Z" }, + { url = "https://files.pythonhosted.org/packages/fb/b5/55c1082bae1f42db68045ed4a81f48734846c7d075536028a9c82dec698a/pygit2-1.18.0-cp313-cp313-win32.whl", hash = "sha256:b92d94807f8c08bede11fa04fbced424b8073cc71603273f1a124b1748c3da40", size = 1221700, upload-time = "2025-04-24T18:46:08.6Z" }, + { url = "https://files.pythonhosted.org/packages/5d/bf/377a37899a46b16492fb6c1136221bf024b488af9656725de1d6344861d3/pygit2-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:43285c57dcdad03114b88a1bc86a0ff7ee216185912c1a0d69aa20c78584fb44", size = 1306953, upload-time = "2025-04-24T18:50:42.731Z" }, + { url = "https://files.pythonhosted.org/packages/08/73/e2186a958fb9dae9baa3b80fa2efe17d65cce8b5dcd00b6c10d305301134/pygit2-1.18.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:081841b01cec4db40ccb0b1ad283aed308e5f663b24995af2b8118c83032539a", size = 5254523, upload-time = "2025-04-24T18:39:59.839Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e6/716cb4188339eca5951bfd9febf5bf8363e460e8b01772b479ed15268ef1/pygit2-1.18.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2acda38a46eb9fa3807ba7790d6f94871b14b43483377fb4db957b58f7ce4732", size = 4985392, upload-time = "2025-04-24T18:40:01.723Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0a/6dda18ff8409efbaedeb1951acf322f6dedcce0fbacc1f7e8776880208c9/pygit2-1.18.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53c897a8f1093961df44cd91208a2b4c33727a1aaf6b5ca22261e75062f678ff", size = 5253372, upload-time = "2025-04-24T18:40:04.748Z" }, + { url = "https://files.pythonhosted.org/packages/c4/30/6d919f673aa0f4220e401b6f22593f4bec73a1a2bde5b3be14d648a6e332/pygit2-1.18.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b07bdd779c892cf4b1212ae9199a64c4416be1a478765f5269c9ba3835540569", size = 4984343, upload-time = "2025-04-24T18:40:06.377Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919, upload-time = "2024-12-01T12:54:25.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083, upload-time = "2024-12-01T12:54:19.735Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "0.25.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/df/adcc0d60f1053d74717d21d58c0048479e9cab51464ce0d2965b086bd0e2/pytest_asyncio-0.25.2.tar.gz", hash = "sha256:3f8ef9a98f45948ea91a0ed3dc4268b5326c0e7bce73892acc654df4262ad45f", size = 53950, upload-time = "2025-01-08T06:20:29.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/d8/defa05ae50dcd6019a95527200d3b3980043df5aa445d40cb0ef9f7f98ab/pytest_asyncio-0.25.2-py3-none-any.whl", hash = "sha256:0d0bb693f7b99da304a0634afc0a4b19e49d5e0de2d670f38dc4bfa5727c5075", size = 19400, upload-time = "2025-01-08T06:20:27.862Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/77/bd458a2e387e98f71de86dcc2ca2cab64489736004c80bc12b70da8b5488/python-dateutil-2.9.0.tar.gz", hash = "sha256:78e73e19c63f5b20ffa567001531680d939dc042bf7850431877645523c66709", size = 342990, upload-time = "2024-03-01T03:52:54.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/7f/98d6f9ca8b731506c85785bbb8806c01f5966a4df6d68c0d1cf3b16967e1/python_dateutil-2.9.0-py2.py3-none-any.whl", hash = "sha256:cbf2f1da5e6083ac2fbfd4da39a25f34312230110440f424a14c7558bb85d82e", size = 230495, upload-time = "2024-03-01T03:52:51.479Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +name = "regex" +version = "2024.11.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494, upload-time = "2024-11-06T20:12:31.635Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/3c/4651f6b130c6842a8f3df82461a8950f923925db8b6961063e82744bddcc/regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91", size = 482674, upload-time = "2024-11-06T20:08:57.575Z" }, + { url = "https://files.pythonhosted.org/packages/15/51/9f35d12da8434b489c7b7bffc205c474a0a9432a889457026e9bc06a297a/regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0", size = 287684, upload-time = "2024-11-06T20:08:59.787Z" }, + { url = "https://files.pythonhosted.org/packages/bd/18/b731f5510d1b8fb63c6b6d3484bfa9a59b84cc578ac8b5172970e05ae07c/regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e", size = 284589, upload-time = "2024-11-06T20:09:01.896Z" }, + { url = "https://files.pythonhosted.org/packages/78/a2/6dd36e16341ab95e4c6073426561b9bfdeb1a9c9b63ab1b579c2e96cb105/regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde", size = 782511, upload-time = "2024-11-06T20:09:04.062Z" }, + { url = "https://files.pythonhosted.org/packages/1b/2b/323e72d5d2fd8de0d9baa443e1ed70363ed7e7b2fb526f5950c5cb99c364/regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e", size = 821149, upload-time = "2024-11-06T20:09:06.237Z" }, + { url = "https://files.pythonhosted.org/packages/90/30/63373b9ea468fbef8a907fd273e5c329b8c9535fee36fc8dba5fecac475d/regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2", size = 809707, upload-time = "2024-11-06T20:09:07.715Z" }, + { url = "https://files.pythonhosted.org/packages/f2/98/26d3830875b53071f1f0ae6d547f1d98e964dd29ad35cbf94439120bb67a/regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf", size = 781702, upload-time = "2024-11-06T20:09:10.101Z" }, + { url = "https://files.pythonhosted.org/packages/87/55/eb2a068334274db86208ab9d5599ffa63631b9f0f67ed70ea7c82a69bbc8/regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c", size = 771976, upload-time = "2024-11-06T20:09:11.566Z" }, + { url = "https://files.pythonhosted.org/packages/74/c0/be707bcfe98254d8f9d2cff55d216e946f4ea48ad2fd8cf1428f8c5332ba/regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86", size = 697397, upload-time = "2024-11-06T20:09:13.119Z" }, + { url = "https://files.pythonhosted.org/packages/49/dc/bb45572ceb49e0f6509f7596e4ba7031f6819ecb26bc7610979af5a77f45/regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67", size = 768726, upload-time = "2024-11-06T20:09:14.85Z" }, + { url = "https://files.pythonhosted.org/packages/5a/db/f43fd75dc4c0c2d96d0881967897926942e935d700863666f3c844a72ce6/regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d", size = 775098, upload-time = "2024-11-06T20:09:16.504Z" }, + { url = "https://files.pythonhosted.org/packages/99/d7/f94154db29ab5a89d69ff893159b19ada89e76b915c1293e98603d39838c/regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2", size = 839325, upload-time = "2024-11-06T20:09:18.698Z" }, + { url = "https://files.pythonhosted.org/packages/f7/17/3cbfab1f23356fbbf07708220ab438a7efa1e0f34195bf857433f79f1788/regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008", size = 843277, upload-time = "2024-11-06T20:09:21.725Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f2/48b393b51900456155de3ad001900f94298965e1cad1c772b87f9cfea011/regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62", size = 773197, upload-time = "2024-11-06T20:09:24.092Z" }, + { url = "https://files.pythonhosted.org/packages/45/3f/ef9589aba93e084cd3f8471fded352826dcae8489b650d0b9b27bc5bba8a/regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e", size = 261714, upload-time = "2024-11-06T20:09:26.36Z" }, + { url = "https://files.pythonhosted.org/packages/42/7e/5f1b92c8468290c465fd50c5318da64319133231415a8aa6ea5ab995a815/regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519", size = 274042, upload-time = "2024-11-06T20:09:28.762Z" }, + { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669, upload-time = "2024-11-06T20:09:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684, upload-time = "2024-11-06T20:09:32.915Z" }, + { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589, upload-time = "2024-11-06T20:09:35.504Z" }, + { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121, upload-time = "2024-11-06T20:09:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275, upload-time = "2024-11-06T20:09:40.371Z" }, + { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257, upload-time = "2024-11-06T20:09:43.059Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727, upload-time = "2024-11-06T20:09:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667, upload-time = "2024-11-06T20:09:49.828Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963, upload-time = "2024-11-06T20:09:51.819Z" }, + { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700, upload-time = "2024-11-06T20:09:53.982Z" }, + { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592, upload-time = "2024-11-06T20:09:56.222Z" }, + { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929, upload-time = "2024-11-06T20:09:58.642Z" }, + { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213, upload-time = "2024-11-06T20:10:00.867Z" }, + { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734, upload-time = "2024-11-06T20:10:03.361Z" }, + { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052, upload-time = "2024-11-06T20:10:05.179Z" }, + { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781, upload-time = "2024-11-06T20:10:07.07Z" }, + { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455, upload-time = "2024-11-06T20:10:09.117Z" }, + { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759, upload-time = "2024-11-06T20:10:11.155Z" }, + { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976, upload-time = "2024-11-06T20:10:13.24Z" }, + { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077, upload-time = "2024-11-06T20:10:15.37Z" }, + { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160, upload-time = "2024-11-06T20:10:19.027Z" }, + { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896, upload-time = "2024-11-06T20:10:21.85Z" }, + { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997, upload-time = "2024-11-06T20:10:24.329Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725, upload-time = "2024-11-06T20:10:28.067Z" }, + { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481, upload-time = "2024-11-06T20:10:31.612Z" }, + { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896, upload-time = "2024-11-06T20:10:34.054Z" }, + { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138, upload-time = "2024-11-06T20:10:36.142Z" }, + { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692, upload-time = "2024-11-06T20:10:38.394Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135, upload-time = "2024-11-06T20:10:40.367Z" }, + { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567, upload-time = "2024-11-06T20:10:43.467Z" }, + { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525, upload-time = "2024-11-06T20:10:45.19Z" }, + { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324, upload-time = "2024-11-06T20:10:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617, upload-time = "2024-11-06T20:10:49.312Z" }, + { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023, upload-time = "2024-11-06T20:10:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072, upload-time = "2024-11-06T20:10:52.926Z" }, + { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130, upload-time = "2024-11-06T20:10:54.828Z" }, + { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857, upload-time = "2024-11-06T20:10:56.634Z" }, + { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006, upload-time = "2024-11-06T20:10:59.369Z" }, + { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650, upload-time = "2024-11-06T20:11:02.042Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545, upload-time = "2024-11-06T20:11:03.933Z" }, + { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045, upload-time = "2024-11-06T20:11:06.497Z" }, + { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182, upload-time = "2024-11-06T20:11:09.06Z" }, + { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733, upload-time = "2024-11-06T20:11:11.256Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122, upload-time = "2024-11-06T20:11:13.161Z" }, + { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545, upload-time = "2024-11-06T20:11:15Z" }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "setuptools" +version = "78.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/9c/42314ee079a3e9c24b27515f9fbc7a3c1d29992c33451779011c74488375/setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d", size = 1368163, upload-time = "2025-04-19T18:23:36.68Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/99/158ad0609729111163fc1f674a5a42f2605371a4cf036d0441070e2f7455/setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561", size = 1256462, upload-time = "2025-04-19T18:23:34.525Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sse-starlette" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/3e/eae74d8d33e3262bae0a7e023bb43d8bdd27980aa3557333f4632611151f/sse_starlette-2.4.1.tar.gz", hash = "sha256:7c8a800a1ca343e9165fc06bbda45c78e4c6166320707ae30b416c42da070926", size = 18635, upload-time = "2025-07-06T09:41:33.631Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/f1/6c7eaa8187ba789a6dd6d74430307478d2a91c23a5452ab339b6fbe15a08/sse_starlette-2.4.1-py3-none-any.whl", hash = "sha256:08b77ea898ab1a13a428b2b6f73cfe6d0e607a7b4e15b9bb23e4a37b087fd39a", size = 10824, upload-time = "2025-07-06T09:41:32.321Z" }, +] + +[[package]] +name = "starlette" +version = "0.47.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/69/662169fdb92fb96ec3eaee218cf540a629d629c86d7993d9651226a6789b/starlette-0.47.1.tar.gz", hash = "sha256:aef012dd2b6be325ffa16698f9dc533614fb1cebd593a906b90dc1025529a79b", size = 2583072, upload-time = "2025-06-21T04:03:17.337Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/95/38ef0cd7fa11eaba6a99b3c4f5ac948d8bc6ff199aabd327a29cc000840c/starlette-0.47.1-py3-none-any.whl", hash = "sha256:5e11c9f5c7c3f24959edbf2dffdc01bba860228acf657129467d8a7468591527", size = 72747, upload-time = "2025-06-21T04:03:15.705Z" }, +] + +[[package]] +name = "tenacity" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/94/91fccdb4b8110642462e653d5dcb27e7b674742ad68efd146367da7bdb10/tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b", size = 47421, upload-time = "2024-07-29T12:12:27.547Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/cb/b86984bed139586d01532a587464b5805f12e397594f19f931c4c2fbfa61/tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539", size = 28169, upload-time = "2024-07-29T12:12:25.825Z" }, +] + +[[package]] +name = "tiktoken" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/cf/756fedf6981e82897f2d570dd25fa597eb3f4459068ae0572d7e888cfd6f/tiktoken-0.9.0.tar.gz", hash = "sha256:d02a5ca6a938e0490e1ff957bc48c8b078c88cb83977be1625b1fd8aac792c5d", size = 35991, upload-time = "2025-02-14T06:03:01.003Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/f3/50ec5709fad61641e4411eb1b9ac55b99801d71f1993c29853f256c726c9/tiktoken-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:586c16358138b96ea804c034b8acf3f5d3f0258bd2bc3b0227af4af5d622e382", size = 1065770, upload-time = "2025-02-14T06:02:01.251Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f8/5a9560a422cf1755b6e0a9a436e14090eeb878d8ec0f80e0cd3d45b78bf4/tiktoken-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9c59ccc528c6c5dd51820b3474402f69d9a9e1d656226848ad68a8d5b2e5108", size = 1009314, upload-time = "2025-02-14T06:02:02.869Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/3ed4cfff8f809cb902900ae686069e029db74567ee10d017cb254df1d598/tiktoken-0.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0968d5beeafbca2a72c595e8385a1a1f8af58feaebb02b227229b69ca5357fd", size = 1143140, upload-time = "2025-02-14T06:02:04.165Z" }, + { url = "https://files.pythonhosted.org/packages/f1/95/cc2c6d79df8f113bdc6c99cdec985a878768120d87d839a34da4bd3ff90a/tiktoken-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a5fb085a6a3b7350b8fc838baf493317ca0e17bd95e8642f95fc69ecfed1de", size = 1197860, upload-time = "2025-02-14T06:02:06.268Z" }, + { url = "https://files.pythonhosted.org/packages/c7/6c/9c1a4cc51573e8867c9381db1814223c09ebb4716779c7f845d48688b9c8/tiktoken-0.9.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15a2752dea63d93b0332fb0ddb05dd909371ededa145fe6a3242f46724fa7990", size = 1259661, upload-time = "2025-02-14T06:02:08.889Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4c/22eb8e9856a2b1808d0a002d171e534eac03f96dbe1161978d7389a59498/tiktoken-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:26113fec3bd7a352e4b33dbaf1bd8948de2507e30bd95a44e2b1156647bc01b4", size = 894026, upload-time = "2025-02-14T06:02:12.841Z" }, + { url = "https://files.pythonhosted.org/packages/4d/ae/4613a59a2a48e761c5161237fc850eb470b4bb93696db89da51b79a871f1/tiktoken-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f32cc56168eac4851109e9b5d327637f15fd662aa30dd79f964b7c39fbadd26e", size = 1065987, upload-time = "2025-02-14T06:02:14.174Z" }, + { url = "https://files.pythonhosted.org/packages/3f/86/55d9d1f5b5a7e1164d0f1538a85529b5fcba2b105f92db3622e5d7de6522/tiktoken-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:45556bc41241e5294063508caf901bf92ba52d8ef9222023f83d2483a3055348", size = 1009155, upload-time = "2025-02-14T06:02:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/03/58/01fb6240df083b7c1916d1dcb024e2b761213c95d576e9f780dfb5625a76/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03935988a91d6d3216e2ec7c645afbb3d870b37bcb67ada1943ec48678e7ee33", size = 1142898, upload-time = "2025-02-14T06:02:16.666Z" }, + { url = "https://files.pythonhosted.org/packages/b1/73/41591c525680cd460a6becf56c9b17468d3711b1df242c53d2c7b2183d16/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b3d80aad8d2c6b9238fc1a5524542087c52b860b10cbf952429ffb714bc1136", size = 1197535, upload-time = "2025-02-14T06:02:18.595Z" }, + { url = "https://files.pythonhosted.org/packages/7d/7c/1069f25521c8f01a1a182f362e5c8e0337907fae91b368b7da9c3e39b810/tiktoken-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b2a21133be05dc116b1d0372af051cd2c6aa1d2188250c9b553f9fa49301b336", size = 1259548, upload-time = "2025-02-14T06:02:20.729Z" }, + { url = "https://files.pythonhosted.org/packages/6f/07/c67ad1724b8e14e2b4c8cca04b15da158733ac60136879131db05dda7c30/tiktoken-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:11a20e67fdf58b0e2dea7b8654a288e481bb4fc0289d3ad21291f8d0849915fb", size = 893895, upload-time = "2025-02-14T06:02:22.67Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e5/21ff33ecfa2101c1bb0f9b6df750553bd873b7fb532ce2cb276ff40b197f/tiktoken-0.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e88f121c1c22b726649ce67c089b90ddda8b9662545a8aeb03cfef15967ddd03", size = 1065073, upload-time = "2025-02-14T06:02:24.768Z" }, + { url = "https://files.pythonhosted.org/packages/8e/03/a95e7b4863ee9ceec1c55983e4cc9558bcfd8f4f80e19c4f8a99642f697d/tiktoken-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a6600660f2f72369acb13a57fb3e212434ed38b045fd8cc6cdd74947b4b5d210", size = 1008075, upload-time = "2025-02-14T06:02:26.92Z" }, + { url = "https://files.pythonhosted.org/packages/40/10/1305bb02a561595088235a513ec73e50b32e74364fef4de519da69bc8010/tiktoken-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95e811743b5dfa74f4b227927ed86cbc57cad4df859cb3b643be797914e41794", size = 1140754, upload-time = "2025-02-14T06:02:28.124Z" }, + { url = "https://files.pythonhosted.org/packages/1b/40/da42522018ca496432ffd02793c3a72a739ac04c3794a4914570c9bb2925/tiktoken-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99376e1370d59bcf6935c933cb9ba64adc29033b7e73f5f7569f3aad86552b22", size = 1196678, upload-time = "2025-02-14T06:02:29.845Z" }, + { url = "https://files.pythonhosted.org/packages/5c/41/1e59dddaae270ba20187ceb8aa52c75b24ffc09f547233991d5fd822838b/tiktoken-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:badb947c32739fb6ddde173e14885fb3de4d32ab9d8c591cbd013c22b4c31dd2", size = 1259283, upload-time = "2025-02-14T06:02:33.838Z" }, + { url = "https://files.pythonhosted.org/packages/5b/64/b16003419a1d7728d0d8c0d56a4c24325e7b10a21a9dd1fc0f7115c02f0a/tiktoken-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:5a62d7a25225bafed786a524c1b9f0910a1128f4232615bf3f8257a73aaa3b16", size = 894897, upload-time = "2025-02-14T06:02:36.265Z" }, + { url = "https://files.pythonhosted.org/packages/7a/11/09d936d37f49f4f494ffe660af44acd2d99eb2429d60a57c71318af214e0/tiktoken-0.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b0e8e05a26eda1249e824156d537015480af7ae222ccb798e5234ae0285dbdb", size = 1064919, upload-time = "2025-02-14T06:02:37.494Z" }, + { url = "https://files.pythonhosted.org/packages/80/0e/f38ba35713edb8d4197ae602e80837d574244ced7fb1b6070b31c29816e0/tiktoken-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:27d457f096f87685195eea0165a1807fae87b97b2161fe8c9b1df5bd74ca6f63", size = 1007877, upload-time = "2025-02-14T06:02:39.516Z" }, + { url = "https://files.pythonhosted.org/packages/fe/82/9197f77421e2a01373e27a79dd36efdd99e6b4115746ecc553318ecafbf0/tiktoken-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cf8ded49cddf825390e36dd1ad35cd49589e8161fdcb52aa25f0583e90a3e01", size = 1140095, upload-time = "2025-02-14T06:02:41.791Z" }, + { url = "https://files.pythonhosted.org/packages/f2/bb/4513da71cac187383541facd0291c4572b03ec23c561de5811781bbd988f/tiktoken-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc156cb314119a8bb9748257a2eaebd5cc0753b6cb491d26694ed42fc7cb3139", size = 1195649, upload-time = "2025-02-14T06:02:43Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5c/74e4c137530dd8504e97e3a41729b1103a4ac29036cbfd3250b11fd29451/tiktoken-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cd69372e8c9dd761f0ab873112aba55a0e3e506332dd9f7522ca466e817b1b7a", size = 1258465, upload-time = "2025-02-14T06:02:45.046Z" }, + { url = "https://files.pythonhosted.org/packages/de/a8/8f499c179ec900783ffe133e9aab10044481679bb9aad78436d239eee716/tiktoken-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:5ea0edb6f83dc56d794723286215918c1cde03712cbbafa0348b33448faf5b95", size = 894669, upload-time = "2025-02-14T06:02:47.341Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/2d/b0fce2b8201635f60e8c95990080f58461cc9ca3d5026de2e900f38a7f21/tokenizers-0.21.2.tar.gz", hash = "sha256:fdc7cffde3e2113ba0e6cc7318c40e3438a4d74bbc62bf04bcc63bdfb082ac77", size = 351545, upload-time = "2025-06-24T10:24:52.449Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/cc/2936e2d45ceb130a21d929743f1e9897514691bec123203e10837972296f/tokenizers-0.21.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:342b5dfb75009f2255ab8dec0041287260fed5ce00c323eb6bab639066fef8ec", size = 2875206, upload-time = "2025-06-24T10:24:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e6/33f41f2cc7861faeba8988e7a77601407bf1d9d28fc79c5903f8f77df587/tokenizers-0.21.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:126df3205d6f3a93fea80c7a8a266a78c1bd8dd2fe043386bafdd7736a23e45f", size = 2732655, upload-time = "2025-06-24T10:24:41.56Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1791eb329c07122a75b01035b1a3aa22ad139f3ce0ece1b059b506d9d9de/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a32cd81be21168bd0d6a0f0962d60177c447a1aa1b1e48fa6ec9fc728ee0b12", size = 3019202, upload-time = "2025-06-24T10:24:31.791Z" }, + { url = "https://files.pythonhosted.org/packages/05/15/fd2d8104faa9f86ac68748e6f7ece0b5eb7983c7efc3a2c197cb98c99030/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8bd8999538c405133c2ab999b83b17c08b7fc1b48c1ada2469964605a709ef91", size = 2934539, upload-time = "2025-06-24T10:24:34.567Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2e/53e8fd053e1f3ffbe579ca5f9546f35ac67cf0039ed357ad7ec57f5f5af0/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e9944e61239b083a41cf8fc42802f855e1dca0f499196df37a8ce219abac6eb", size = 3248665, upload-time = "2025-06-24T10:24:39.024Z" }, + { url = "https://files.pythonhosted.org/packages/00/15/79713359f4037aa8f4d1f06ffca35312ac83629da062670e8830917e2153/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:514cd43045c5d546f01142ff9c79a96ea69e4b5cda09e3027708cb2e6d5762ab", size = 3451305, upload-time = "2025-06-24T10:24:36.133Z" }, + { url = "https://files.pythonhosted.org/packages/38/5f/959f3a8756fc9396aeb704292777b84f02a5c6f25c3fc3ba7530db5feb2c/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1b9405822527ec1e0f7d8d2fdb287a5730c3a6518189c968254a8441b21faae", size = 3214757, upload-time = "2025-06-24T10:24:37.784Z" }, + { url = "https://files.pythonhosted.org/packages/c5/74/f41a432a0733f61f3d21b288de6dfa78f7acff309c6f0f323b2833e9189f/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed9a4d51c395103ad24f8e7eb976811c57fbec2af9f133df471afcd922e5020", size = 3121887, upload-time = "2025-06-24T10:24:40.293Z" }, + { url = "https://files.pythonhosted.org/packages/3c/6a/bc220a11a17e5d07b0dfb3b5c628621d4dcc084bccd27cfaead659963016/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2c41862df3d873665ec78b6be36fcc30a26e3d4902e9dd8608ed61d49a48bc19", size = 9091965, upload-time = "2025-06-24T10:24:44.431Z" }, + { url = "https://files.pythonhosted.org/packages/6c/bd/ac386d79c4ef20dc6f39c4706640c24823dca7ebb6f703bfe6b5f0292d88/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed21dc7e624e4220e21758b2e62893be7101453525e3d23264081c9ef9a6d00d", size = 9053372, upload-time = "2025-06-24T10:24:46.455Z" }, + { url = "https://files.pythonhosted.org/packages/63/7b/5440bf203b2a5358f074408f7f9c42884849cd9972879e10ee6b7a8c3b3d/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:0e73770507e65a0e0e2a1affd6b03c36e3bc4377bd10c9ccf51a82c77c0fe365", size = 9298632, upload-time = "2025-06-24T10:24:48.446Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d2/faa1acac3f96a7427866e94ed4289949b2524f0c1878512516567d80563c/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:106746e8aa9014a12109e58d540ad5465b4c183768ea96c03cbc24c44d329958", size = 9470074, upload-time = "2025-06-24T10:24:50.378Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a5/896e1ef0707212745ae9f37e84c7d50269411aef2e9ccd0de63623feecdf/tokenizers-0.21.2-cp39-abi3-win32.whl", hash = "sha256:cabda5a6d15d620b6dfe711e1af52205266d05b379ea85a8a301b3593c60e962", size = 2330115, upload-time = "2025-06-24T10:24:55.069Z" }, + { url = "https://files.pythonhosted.org/packages/13/c3/cc2755ee10be859c4338c962a35b9a663788c0c0b50c0bdd8078fb6870cf/tokenizers-0.21.2-cp39-abi3-win_amd64.whl", hash = "sha256:58747bb898acdb1007f37a7bbe614346e98dc28708ffb66a3fd50ce169ac6c98", size = 2509918, upload-time = "2025-06-24T10:24:53.71Z" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "tree-sitter" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/a2/698b9d31d08ad5558f8bfbfe3a0781bd4b1f284e89bde3ad18e05101a892/tree-sitter-0.24.0.tar.gz", hash = "sha256:abd95af65ca2f4f7eca356343391ed669e764f37748b5352946f00f7fc78e734", size = 168304, upload-time = "2025-01-17T05:06:38.115Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/9a/bd627a02e41671af73222316e1fcf87772c7804dc2fba99405275eb1f3eb/tree_sitter-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f3f00feff1fc47a8e4863561b8da8f5e023d382dd31ed3e43cd11d4cae445445", size = 140890, upload-time = "2025-01-17T05:05:42.659Z" }, + { url = "https://files.pythonhosted.org/packages/5b/9b/b1ccfb187f8be78e2116176a091a2f2abfd043a06d78f80c97c97f315b37/tree_sitter-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f9691be48d98c49ef8f498460278884c666b44129222ed6217477dffad5d4831", size = 134413, upload-time = "2025-01-17T05:05:45.241Z" }, + { url = "https://files.pythonhosted.org/packages/01/39/e25b0042a049eb27e991133a7aa7c49bb8e49a8a7b44ca34e7e6353ba7ac/tree_sitter-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:098a81df9f89cf254d92c1cd0660a838593f85d7505b28249216661d87adde4a", size = 560427, upload-time = "2025-01-17T05:05:46.479Z" }, + { url = "https://files.pythonhosted.org/packages/1c/59/4d132f1388da5242151b90acf32cc56af779bfba063923699ab28b276b62/tree_sitter-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b26bf9e958da6eb7e74a081aab9d9c7d05f9baeaa830dbb67481898fd16f1f5", size = 574327, upload-time = "2025-01-17T05:05:48.93Z" }, + { url = "https://files.pythonhosted.org/packages/ec/97/3914e45ab9e0ff0f157e493caa91791372508488b97ff0961a0640a37d25/tree_sitter-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2a84ff87a2f2a008867a1064aba510ab3bd608e3e0cd6e8fef0379efee266c73", size = 577171, upload-time = "2025-01-17T05:05:51.588Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b0/266a529c3eef171137b73cde8ad7aa282734354609a8b2f5564428e8f12d/tree_sitter-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:c012e4c345c57a95d92ab5a890c637aaa51ab3b7ff25ed7069834b1087361c95", size = 120260, upload-time = "2025-01-17T05:05:53.994Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c3/07bfaa345e0037ff75d98b7a643cf940146e4092a1fd54eed0359836be03/tree_sitter-0.24.0-cp310-cp310-win_arm64.whl", hash = "sha256:033506c1bc2ba7bd559b23a6bdbeaf1127cee3c68a094b82396718596dfe98bc", size = 108416, upload-time = "2025-01-17T05:05:55.056Z" }, + { url = "https://files.pythonhosted.org/packages/66/08/82aaf7cbea7286ee2a0b43e9b75cb93ac6ac132991b7d3c26ebe5e5235a3/tree_sitter-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de0fb7c18c6068cacff46250c0a0473e8fc74d673e3e86555f131c2c1346fb13", size = 140733, upload-time = "2025-01-17T05:05:56.307Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bd/1a84574911c40734d80327495e6e218e8f17ef318dd62bb66b55c1e969f5/tree_sitter-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7c9c89666dea2ce2b2bf98e75f429d2876c569fab966afefdcd71974c6d8538", size = 134243, upload-time = "2025-01-17T05:05:58.706Z" }, + { url = "https://files.pythonhosted.org/packages/46/c1/c2037af2c44996d7bde84eb1c9e42308cc84b547dd6da7f8a8bea33007e1/tree_sitter-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ddb113e6b8b3e3b199695b1492a47d87d06c538e63050823d90ef13cac585fd", size = 562030, upload-time = "2025-01-17T05:05:59.825Z" }, + { url = "https://files.pythonhosted.org/packages/4c/aa/2fb4d81886df958e6ec7e370895f7106d46d0bbdcc531768326124dc8972/tree_sitter-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ea01a7003b88b92f7f875da6ba9d5d741e0c84bb1bd92c503c0eecd0ee6409", size = 575585, upload-time = "2025-01-17T05:06:01.045Z" }, + { url = "https://files.pythonhosted.org/packages/e3/3c/5f997ce34c0d1b744e0f0c0757113bdfc173a2e3dadda92c751685cfcbd1/tree_sitter-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:464fa5b2cac63608915a9de8a6efd67a4da1929e603ea86abaeae2cb1fe89921", size = 578203, upload-time = "2025-01-17T05:06:02.255Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1f/f2bc7fa7c3081653ea4f2639e06ff0af4616c47105dbcc0746137da7620d/tree_sitter-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:3b1f3cbd9700e1fba0be2e7d801527e37c49fc02dc140714669144ef6ab58dce", size = 120147, upload-time = "2025-01-17T05:06:05.233Z" }, + { url = "https://files.pythonhosted.org/packages/c0/4c/9add771772c4d72a328e656367ca948e389432548696a3819b69cdd6f41e/tree_sitter-0.24.0-cp311-cp311-win_arm64.whl", hash = "sha256:f3f08a2ca9f600b3758792ba2406971665ffbad810847398d180c48cee174ee2", size = 108302, upload-time = "2025-01-17T05:06:07.487Z" }, + { url = "https://files.pythonhosted.org/packages/e9/57/3a590f287b5aa60c07d5545953912be3d252481bf5e178f750db75572bff/tree_sitter-0.24.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14beeff5f11e223c37be7d5d119819880601a80d0399abe8c738ae2288804afc", size = 140788, upload-time = "2025-01-17T05:06:08.492Z" }, + { url = "https://files.pythonhosted.org/packages/61/0b/fc289e0cba7dbe77c6655a4dd949cd23c663fd62a8b4d8f02f97e28d7fe5/tree_sitter-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26a5b130f70d5925d67b47db314da209063664585a2fd36fa69e0717738efaf4", size = 133945, upload-time = "2025-01-17T05:06:12.39Z" }, + { url = "https://files.pythonhosted.org/packages/86/d7/80767238308a137e0b5b5c947aa243e3c1e3e430e6d0d5ae94b9a9ffd1a2/tree_sitter-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fc5c3c26d83c9d0ecb4fc4304fba35f034b7761d35286b936c1db1217558b4e", size = 564819, upload-time = "2025-01-17T05:06:13.549Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b3/6c5574f4b937b836601f5fb556b24804b0a6341f2eb42f40c0e6464339f4/tree_sitter-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:772e1bd8c0931c866b848d0369b32218ac97c24b04790ec4b0e409901945dd8e", size = 579303, upload-time = "2025-01-17T05:06:16.685Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f4/bd0ddf9abe242ea67cca18a64810f8af230fc1ea74b28bb702e838ccd874/tree_sitter-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:24a8dd03b0d6b8812425f3b84d2f4763322684e38baf74e5bb766128b5633dc7", size = 581054, upload-time = "2025-01-17T05:06:19.439Z" }, + { url = "https://files.pythonhosted.org/packages/8c/1c/ff23fa4931b6ef1bbeac461b904ca7e49eaec7e7e5398584e3eef836ec96/tree_sitter-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:f9e8b1605ab60ed43803100f067eed71b0b0e6c1fb9860a262727dbfbbb74751", size = 120221, upload-time = "2025-01-17T05:06:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2a/9979c626f303177b7612a802237d0533155bf1e425ff6f73cc40f25453e2/tree_sitter-0.24.0-cp312-cp312-win_arm64.whl", hash = "sha256:f733a83d8355fc95561582b66bbea92ffd365c5d7a665bc9ebd25e049c2b2abb", size = 108234, upload-time = "2025-01-17T05:06:21.713Z" }, + { url = "https://files.pythonhosted.org/packages/61/cd/2348339c85803330ce38cee1c6cbbfa78a656b34ff58606ebaf5c9e83bd0/tree_sitter-0.24.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d4a6416ed421c4210f0ca405a4834d5ccfbb8ad6692d4d74f7773ef68f92071", size = 140781, upload-time = "2025-01-17T05:06:22.82Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a3/1ea9d8b64e8dcfcc0051028a9c84a630301290995cd6e947bf88267ef7b1/tree_sitter-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0992d483677e71d5c5d37f30dfb2e3afec2f932a9c53eec4fca13869b788c6c", size = 133928, upload-time = "2025-01-17T05:06:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/55c1055609c9428a4aedf4b164400ab9adb0b1bf1538b51f4b3748a6c983/tree_sitter-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57277a12fbcefb1c8b206186068d456c600dbfbc3fd6c76968ee22614c5cd5ad", size = 564497, upload-time = "2025-01-17T05:06:27.53Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d0/f2ffcd04882c5aa28d205a787353130cbf84b2b8a977fd211bdc3b399ae3/tree_sitter-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25fa22766d63f73716c6fec1a31ee5cf904aa429484256bd5fdf5259051ed74", size = 578917, upload-time = "2025-01-17T05:06:31.057Z" }, + { url = "https://files.pythonhosted.org/packages/af/82/aebe78ea23a2b3a79324993d4915f3093ad1af43d7c2208ee90be9273273/tree_sitter-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7d5d9537507e1c8c5fa9935b34f320bfec4114d675e028f3ad94f11cf9db37b9", size = 581148, upload-time = "2025-01-17T05:06:32.409Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b4/6b0291a590c2b0417cfdb64ccb8ea242f270a46ed429c641fbc2bfab77e0/tree_sitter-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:f58bb4956917715ec4d5a28681829a8dad5c342cafd4aea269f9132a83ca9b34", size = 120207, upload-time = "2025-01-17T05:06:34.841Z" }, + { url = "https://files.pythonhosted.org/packages/a8/18/542fd844b75272630229c9939b03f7db232c71a9d82aadc59c596319ea6a/tree_sitter-0.24.0-cp313-cp313-win_arm64.whl", hash = "sha256:23641bd25dcd4bb0b6fa91b8fb3f46cc9f1c9f475efe4d536d3f1f688d1b84c8", size = 108232, upload-time = "2025-01-17T05:06:35.831Z" }, +] + +[[package]] +name = "tree-sitter-python" +version = "0.23.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/30/6766433b31be476fda6569a3a374c2220e45ffee0bff75460038a57bf23b/tree_sitter_python-0.23.6.tar.gz", hash = "sha256:354bfa0a2f9217431764a631516f85173e9711af2c13dbd796a8815acfe505d9", size = 155868, upload-time = "2024-12-22T23:09:55.918Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/67/577a02acae5f776007c924ca86ef14c19c12e71de0aa9d2a036f3c248e7b/tree_sitter_python-0.23.6-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:28fbec8f74eeb2b30292d97715e60fac9ccf8a8091ce19b9d93e9b580ed280fb", size = 74361, upload-time = "2024-12-22T23:09:42.37Z" }, + { url = "https://files.pythonhosted.org/packages/d2/a6/194b3625a7245c532ad418130d63077ce6cd241152524152f533e4d6edb0/tree_sitter_python-0.23.6-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:680b710051b144fedf61c95197db0094f2245e82551bf7f0c501356333571f7a", size = 76436, upload-time = "2024-12-22T23:09:43.566Z" }, + { url = "https://files.pythonhosted.org/packages/d0/62/1da112689d6d282920e62c40e67ab39ea56463b0e7167bfc5e81818a770e/tree_sitter_python-0.23.6-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a9dcef55507b6567207e8ee0a6b053d0688019b47ff7f26edc1764b7f4dc0a4", size = 112060, upload-time = "2024-12-22T23:09:44.721Z" }, + { url = "https://files.pythonhosted.org/packages/5d/62/c9358584c96e38318d69b6704653684fd8467601f7b74e88aa44f4e6903f/tree_sitter_python-0.23.6-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29dacdc0cd2f64e55e61d96c6906533ebb2791972bec988450c46cce60092f5d", size = 112338, upload-time = "2024-12-22T23:09:48.323Z" }, + { url = "https://files.pythonhosted.org/packages/1a/58/c5e61add45e34fb8ecbf057c500bae9d96ed7c9ca36edb7985da8ae45526/tree_sitter_python-0.23.6-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7e048733c36f564b379831689006801feb267d8194f9e793fbb395ef1723335d", size = 109382, upload-time = "2024-12-22T23:09:49.49Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f3/9b30893cae9b3811fe652dc6f90aaadfda12ae0b2757f5722fc7266f423c/tree_sitter_python-0.23.6-cp39-abi3-win_amd64.whl", hash = "sha256:a24027248399fb41594b696f929f9956828ae7cc85596d9f775e6c239cd0c2be", size = 75904, upload-time = "2024-12-22T23:09:51.597Z" }, + { url = "https://files.pythonhosted.org/packages/87/cb/ce35a65f83a47b510d8a2f1eddf3bdbb0d57aabc87351c8788caf3309f76/tree_sitter_python-0.23.6-cp39-abi3-win_arm64.whl", hash = "sha256:71334371bd73d5fe080aed39fbff49ed8efb9506edebe16795b0c7567ed6a272", size = 73649, upload-time = "2024-12-22T23:09:53.71Z" }, +] + +[[package]] +name = "tree-sitter-typescript" +version = "0.23.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/fc/bb52958f7e399250aee093751e9373a6311cadbe76b6e0d109b853757f35/tree_sitter_typescript-0.23.2.tar.gz", hash = "sha256:7b167b5827c882261cb7a50dfa0fb567975f9b315e87ed87ad0a0a3aedb3834d", size = 773053, upload-time = "2024-11-11T02:36:11.396Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/95/4c00680866280e008e81dd621fd4d3f54aa3dad1b76b857a19da1b2cc426/tree_sitter_typescript-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3cd752d70d8e5371fdac6a9a4df9d8924b63b6998d268586f7d374c9fba2a478", size = 286677, upload-time = "2024-11-11T02:35:58.839Z" }, + { url = "https://files.pythonhosted.org/packages/8f/2f/1f36fda564518d84593f2740d5905ac127d590baf5c5753cef2a88a89c15/tree_sitter_typescript-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c7cc1b0ff5d91bac863b0e38b1578d5505e718156c9db577c8baea2557f66de8", size = 302008, upload-time = "2024-11-11T02:36:00.733Z" }, + { url = "https://files.pythonhosted.org/packages/96/2d/975c2dad292aa9994f982eb0b69cc6fda0223e4b6c4ea714550477d8ec3a/tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b1eed5b0b3a8134e86126b00b743d667ec27c63fc9de1b7bb23168803879e31", size = 351987, upload-time = "2024-11-11T02:36:02.669Z" }, + { url = "https://files.pythonhosted.org/packages/49/d1/a71c36da6e2b8a4ed5e2970819b86ef13ba77ac40d9e333cb17df6a2c5db/tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e96d36b85bcacdeb8ff5c2618d75593ef12ebaf1b4eace3477e2bdb2abb1752c", size = 344960, upload-time = "2024-11-11T02:36:04.443Z" }, + { url = "https://files.pythonhosted.org/packages/7f/cb/f57b149d7beed1a85b8266d0c60ebe4c46e79c9ba56bc17b898e17daf88e/tree_sitter_typescript-0.23.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8d4f0f9bcb61ad7b7509d49a1565ff2cc363863644a234e1e0fe10960e55aea0", size = 340245, upload-time = "2024-11-11T02:36:06.473Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ab/dd84f0e2337296a5f09749f7b5483215d75c8fa9e33738522e5ed81f7254/tree_sitter_typescript-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:3f730b66396bc3e11811e4465c41ee45d9e9edd6de355a58bbbc49fa770da8f9", size = 278015, upload-time = "2024-11-11T02:36:07.631Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e4/81f9a935789233cf412a0ed5fe04c883841d2c8fb0b7e075958a35c65032/tree_sitter_typescript-0.23.2-cp39-abi3-win_arm64.whl", hash = "sha256:05db58f70b95ef0ea126db5560f3775692f609589ed6f8dd0af84b7f19f1cbb7", size = 274052, upload-time = "2024-11-11T02:36:09.514Z" }, +] + +[[package]] +name = "typer" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/8c/7d682431efca5fd290017663ea4588bf6f2c6aad085c7f108c5dbc316e70/typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b", size = 102625, upload-time = "2025-05-26T14:30:31.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317, upload-time = "2025-05-26T14:30:30.523Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, +] + +[[package]] +name = "ulid" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/d4/6829692e4902d53684b7696cf3d5158f07439ebc4bc3ca7822b5ca173e44/ulid-1.1.tar.gz", hash = "sha256:0943e8a751ec10dfcdb4df2758f96dffbbfbc055d0b49288caf2f92125900d49", size = 1174, upload-time = "2016-08-01T23:00:12.792Z" } + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/42/e0e305207bb88c6b8d3061399c6a961ffe5fbb7e2aa63c9234df7259e9cd/uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01", size = 78473, upload-time = "2025-06-28T16:15:46.058Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" }, + { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" }, + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" }, + { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" }, + { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" }, + { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "win32-setctime" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, +] From 3d84d24997ca11017861a79e6b9aeee9d37b0189 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 20 Jul 2025 13:00:59 +0100 Subject: [PATCH 86/88] updated README --- README.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 230a69a..8c07c84 100644 --- a/README.md +++ b/README.md @@ -38,20 +38,17 @@ CodeTide is available as a native [**Visual Studio Code extension**](https://mar ## 🖧 CodeTide as an MCP Server -CodeTide now supports acting as an **MCP (Multi-Codebase Processing) Server**, enabling seamless integration with AI agents and tools. This feature allows agents to dynamically interact with your codebase, retrieve context, and apply changes efficiently. +CodeTide now supports acting as an **MCP Server**, enabling seamless integration with AI agents and tools. This feature allows agents to dynamically interact with your codebase and retrieve context efficiently. #### Why This Helps Agents Agents working with codebases often need: - **Contextual Understanding**: Retrieve declarations, imports, and references for any part of the code. -- **Tool Integration**: Use built-in tools to navigate, modify, and validate code. -- **Atomic Operations**: Apply patches or updates without manual intervention. +- **Tool Integration**: Use built-in tools to navigate and analyze code. #### Available Tools CodeTide provides the following tools for agents: -1. **`applyPatch`**: Apply structured patches to files. -2. **`getContext`**: Retrieve code context for identifiers (e.g., functions, classes). -3. **`getRepoTree`**: Explore the repository structure. -4. **`checkCodeIdentifiers`**: Validate and suggest corrections for code identifiers. +1. **`getContext`**: Retrieve code context for identifiers (e.g., functions, classes). +2. **`getRepoTree`**: Explore the repository structure. #### Example: Initializing an LLM with CodeTide Here’s a snippet from `agent_tide.py` demonstrating how to initialize an LLM with CodeTide as an MCP server: @@ -77,11 +74,9 @@ def init_llm() -> Llm: This setup allows the LLM to leverage CodeTide’s tools for codebase interactions. CodeTide can now be used as an MCP (Multi-Code Processor) Server! This allows seamless integration with AI tools and workflows. Below are the tools available: - -- **applyPatch**: Apply structured patches to the filesystem. +The available tools are: - **getContext**: Retrieve code context for identifiers. - **getRepoTree**: Generate a visual tree representation of the repository. -- **checkCodeIdentifiers**: Validate code identifiers and suggest corrections. ## ⚙️ Installation From 0620ef212772e32b06c05cc91822c079968d68c9 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 20 Jul 2025 13:01:15 +0100 Subject: [PATCH 87/88] added mcp server instructions --- codetide/mcp/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codetide/mcp/server.py b/codetide/mcp/server.py index 134a54d..8cdcaae 100644 --- a/codetide/mcp/server.py +++ b/codetide/mcp/server.py @@ -3,7 +3,7 @@ codeTideMCPServer = FastMCP( name="codetide", - # instructions=AGENT_TIDE_SYSTEM_PROMPT, + instructions="Use this server to retrieve code context and explore the repository structure using the getContext and getRepoTree tools." ) def serve(): From c25260df1b2d808415058e824b31c8a08f1d8c85 Mon Sep 17 00:00:00 2001 From: BrunoV21 Date: Sun, 20 Jul 2025 13:02:27 +0100 Subject: [PATCH 88/88] version bump to 0.0.22 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f69a669..f18b267 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup( name="codetide", - version="0.0.21", + version="0.0.22", author="Bruno V.", author_email="bruno.vitorino@tecnico.ulisboa.pt", description="CodeTide is a fully local, privacy-preserving tool for parsing and understanding Python codebases using symbolic, structural analysis. No internet, no LLMs, no embeddings - just fast, explainable, and deterministic code intelligence.",