├── autolunch ├── __init__.py ├── cli.py └── app.py ├── screenshot.png ├── .github └── workflows │ ├── lint.yaml │ ├── publish.yaml │ └── build.yaml ├── README.md ├── pyproject.toml ├── LICENSE ├── .gitignore └── pdm.lock /autolunch/__init__.py: -------------------------------------------------------------------------------- 1 | __application__ = "lunchable-autolunch" 2 | __version__ = "0.1.2" 3 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PhotonQuantum/lunchable-autolunch/master/screenshot.png -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: [ push, pull_request ] 3 | jobs: 4 | lint: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v4 8 | - uses: astral-sh/ruff-action@v3 9 | - run: ruff format --check --diff -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | pypi-publish: 9 | name: upload release to PyPI 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read 13 | id-token: write 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: pdm-project/setup-pdm@v4 17 | - name: Publish package distributions to PyPI 18 | run: pdm publish -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [ push, pull_request ] 3 | 4 | jobs: 5 | build: 6 | runs-on: ${{ matrix.os }} 7 | strategy: 8 | matrix: 9 | python-version: [ '3.10', '3.11', '3.12', '3.13' ] 10 | os: [ ubuntu-latest, macOS-latest, windows-latest ] 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Set up PDM 15 | uses: pdm-project/setup-pdm@v4 16 | with: 17 | python-version: ${{ matrix.python-version }} 18 | - name: Install dependencies 19 | run: | 20 | pdm sync -d 21 | - name: Build package 22 | run: | 23 | pdm build 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lunchable-autolunch 2 | 3 | Automatically rename merchant names and autocategorise transactions for LunchMoney. 4 | 5 | ![screenshot](screenshot.png) 6 | 7 | ## Installation 8 | 9 | ```shell 10 | pip install lunchable-autolunch 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```shell 16 | export LUNCHMONEY_ACCESS_TOKEN=... 17 | export OPENAI_API_KEY=... 18 | lunchable plugins autolunch classify 19 | ``` 20 | 21 | ## Missing Lunch Money API 22 | 23 | The Lunch Money API is missing a few key features that would make this plugin more useful: 24 | - Rule API (and the ability to create multiple patterns on the same field/regex support) 25 | - Filter out uncategorised transactions -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "lunchable-autolunch" 3 | version = "0.1.2" 4 | description = "Automatically rename merchant names and autocategorise transactions." 5 | authors = [ 6 | {name = "LightQuantum", email = "self@lightquantum.me"}, 7 | ] 8 | dependencies = ["click>=8.1.8", "lunchable>=1.4.2", "rich>=13.9.4", "openai>=1.68.2", "questionary>=2.1.0", "pydantic>=2.10.6", "readchar>=4.2.1", "copykitten>=1.2.2", "fuzzyfinder>=2.2.0", "thefuzz>=0.22.1"] 9 | requires-python = ">=3.10" 10 | readme = "README.md" 11 | license = {text = "MIT"} 12 | 13 | [build-system] 14 | requires = ["pdm-backend"] 15 | build-backend = "pdm.backend" 16 | 17 | 18 | [project.entry-points."lunchable.cli"] 19 | autolunch = "autolunch.cli:autolunch" 20 | 21 | 22 | [tool.pdm] 23 | distribution = true 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 LightQuantum 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /autolunch/cli.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from datetime import datetime 4 | from typing import Optional 5 | 6 | import click 7 | import questionary 8 | from lunchable.models import AssetsObject 9 | from pydantic import TypeAdapter 10 | 11 | from autolunch.app import AutoLunchApp, Rule 12 | 13 | 14 | def text_truncate_pad(s: str, length: int) -> str: 15 | if len(s) > length: 16 | return s[: (length - 3) // 2] + "..." + s[-(length - 3) // 2 :] 17 | else: 18 | return s.ljust(length) 19 | 20 | 21 | @click.group 22 | def autolunch(): 23 | """ 24 | Automatically classify uncategorized transactions using GPT. 25 | """ 26 | pass 27 | 28 | 29 | @autolunch.command 30 | @click.option( 31 | "--ruleset", 32 | type=click.STRING, 33 | help="Path to the ruleset file", 34 | default="ruleset.json", 35 | ) 36 | @click.option( 37 | "--openai-api-key", 38 | type=click.STRING, 39 | help=("OpenAI API Key - defaults to the OPENAI_API_KEY environment variable"), 40 | envvar="OPENAI_API_KEY", 41 | ) 42 | @click.option( 43 | "-t", 44 | "--token", 45 | "access_token", 46 | type=click.STRING, 47 | help=( 48 | "LunchMoney Access Token - defaults to the " 49 | "LUNCHMONEY_ACCESS_TOKEN environment variable" 50 | ), 51 | envvar="LUNCHMONEY_ACCESS_TOKEN", 52 | ) 53 | @click.option( 54 | "--since", 55 | type=click.DateTime(formats=["%Y-%m-%d"]), 56 | default=None, 57 | help=( 58 | "Start date for transactions - defaults to the beginning of the current month" 59 | ), 60 | ) 61 | @click.option( 62 | "--until", 63 | type=click.DateTime(formats=["%Y-%m-%d"]), 64 | default=None, 65 | help=("End date for transactions - defaults to the current date"), 66 | ) 67 | def classify( 68 | ruleset: str, 69 | openai_api_key: str, 70 | access_token: str, 71 | since: Optional[datetime], 72 | until: Optional[datetime], 73 | ): 74 | """ 75 | Classify uncategorized transactions. 76 | """ 77 | app = AutoLunchApp(openai_api_key=openai_api_key, access_token=access_token) 78 | app.set_since_until(since, until) 79 | app.refresh_data() 80 | 81 | if not os.path.exists(ruleset): 82 | with open(ruleset, "w") as f: 83 | json.dump([], f) 84 | 85 | with open(ruleset, "r") as f: 86 | s = f.read() 87 | app.rules = TypeAdapter(list[Rule]).validate_json(s) 88 | 89 | choices = [ 90 | questionary.Choice( 91 | title=[ 92 | ( 93 | "class:text", 94 | text_truncate_pad( 95 | asset.display_name if asset.display_name else asset.name, 30 96 | ), 97 | ), 98 | ("class:text", " "), 99 | ( 100 | "class:type", 101 | text_truncate_pad( 102 | "[" 103 | + ( 104 | asset.type_name 105 | if isinstance(asset, AssetsObject) 106 | else asset.type 107 | ) 108 | + "]", 109 | 10, 110 | ), 111 | ), 112 | ("class:text", " "), 113 | ("class:balance", "$" + str(asset.balance)), 114 | ], 115 | value=asset, 116 | ) 117 | for asset in app.data.asset_map.values() 118 | ] 119 | style = questionary.Style([("type", "fg:#008b8b"), ("balance", "fg:#008000")]) 120 | account = questionary.select( 121 | "Which account do you want to classify transactions for?", 122 | choices=choices, 123 | style=style, 124 | ).ask() 125 | if not account: 126 | return 127 | app.set_account(account) 128 | 129 | mode = questionary.select( 130 | "Is this a credit or debit account?", choices=["Credit", "Debit"] 131 | ).ask() 132 | app.set_mode(mode.lower()) 133 | 134 | while rules := app.suggest_rules(20): 135 | choices = [ 136 | questionary.Choice( 137 | title=[ 138 | ("class:text", text_truncate_pad(original, 60)), 139 | ("class:text", " "), 140 | ("class:emph", text_truncate_pad(rule.name, 30)), 141 | ("class:text", " "), 142 | ("class:emph", text_truncate_pad(rule.category, 30)), 143 | ("class:text", " "), 144 | ("class:pattern", text_truncate_pad(str(rule.matchers), 60)), 145 | ], 146 | value=rule, 147 | checked=rule.name != "Unknown" and rule.category != "Unknown", 148 | ) 149 | for original, rule in rules.items() 150 | ] 151 | style = questionary.Style([("emph", "fg:#008b8b"), ("pattern", "fg:#808080")]) 152 | checked = questionary.checkbox( 153 | "Select rules to apply", choices=choices, style=style 154 | ).ask() 155 | if checked is None: 156 | exit_or_rerun = questionary.select( 157 | "Would you like to rerun or exit the rule suggestion?", 158 | ["Rerun", "Exit"], 159 | ).ask() 160 | if exit_or_rerun == "Exit": 161 | break 162 | else: 163 | app.add_rules(checked) 164 | with open(ruleset, "w") as f: 165 | json.dump([rule.model_dump() for rule in app.rules], f) 166 | 167 | 168 | if __name__ == "__main__": 169 | autolunch() 170 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm-project.org/#use-with-ide 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | ### Python template 164 | # Byte-compiled / optimized / DLL files 165 | __pycache__/ 166 | *.py[cod] 167 | *$py.class 168 | 169 | # C extensions 170 | *.so 171 | 172 | # Distribution / packaging 173 | .Python 174 | build/ 175 | develop-eggs/ 176 | dist/ 177 | downloads/ 178 | eggs/ 179 | .eggs/ 180 | lib/ 181 | lib64/ 182 | parts/ 183 | sdist/ 184 | var/ 185 | wheels/ 186 | share/python-wheels/ 187 | *.egg-info/ 188 | .installed.cfg 189 | *.egg 190 | MANIFEST 191 | 192 | # PyInstaller 193 | # Usually these files are written by a python script from a template 194 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 195 | *.manifest 196 | *.spec 197 | 198 | # Installer logs 199 | pip-log.txt 200 | pip-delete-this-directory.txt 201 | 202 | # Unit test / coverage reports 203 | htmlcov/ 204 | .tox/ 205 | .nox/ 206 | .coverage 207 | .coverage.* 208 | .cache 209 | nosetests.xml 210 | coverage.xml 211 | *.cover 212 | *.py,cover 213 | .hypothesis/ 214 | .pytest_cache/ 215 | cover/ 216 | 217 | # Translations 218 | *.mo 219 | *.pot 220 | 221 | # Django stuff: 222 | *.log 223 | local_settings.py 224 | db.sqlite3 225 | db.sqlite3-journal 226 | 227 | # Flask stuff: 228 | instance/ 229 | .webassets-cache 230 | 231 | # Scrapy stuff: 232 | .scrapy 233 | 234 | # Sphinx documentation 235 | docs/_build/ 236 | 237 | # PyBuilder 238 | .pybuilder/ 239 | target/ 240 | 241 | # Jupyter Notebook 242 | .ipynb_checkpoints 243 | 244 | # IPython 245 | profile_default/ 246 | ipython_config.py 247 | 248 | # pyenv 249 | # For a library or package, you might want to ignore these files since the code is 250 | # intended to run in multiple environments; otherwise, check them in: 251 | # .python-version 252 | 253 | # pipenv 254 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 255 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 256 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 257 | # install all needed dependencies. 258 | #Pipfile.lock 259 | 260 | # poetry 261 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 262 | # This is especially recommended for binary packages to ensure reproducibility, and is more 263 | # commonly ignored for libraries. 264 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 265 | #poetry.lock 266 | 267 | # pdm 268 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 269 | #pdm.lock 270 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 271 | # in version control. 272 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 273 | .pdm.toml 274 | .pdm-python 275 | .pdm-build/ 276 | 277 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 278 | __pypackages__/ 279 | 280 | # Celery stuff 281 | celerybeat-schedule 282 | celerybeat.pid 283 | 284 | # SageMath parsed files 285 | *.sage.py 286 | 287 | # Environments 288 | .env 289 | .venv 290 | env/ 291 | venv/ 292 | ENV/ 293 | env.bak/ 294 | venv.bak/ 295 | 296 | # Spyder project settings 297 | .spyderproject 298 | .spyproject 299 | 300 | # Rope project settings 301 | .ropeproject 302 | 303 | # mkdocs documentation 304 | /site 305 | 306 | # mypy 307 | .mypy_cache/ 308 | .dmypy.json 309 | dmypy.json 310 | 311 | # Pyre type checker 312 | .pyre/ 313 | 314 | # pytype static type analyzer 315 | .pytype/ 316 | 317 | # Cython debug symbols 318 | cython_debug/ 319 | 320 | # PyCharm 321 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 322 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 323 | # and can be added to the global gitignore or merged into this file. For a more nuclear 324 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 325 | #.idea/ 326 | 327 | ruleset.json -------------------------------------------------------------------------------- /autolunch/app.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import itertools 3 | import json 4 | import typing 5 | from collections import OrderedDict 6 | from typing import Optional, Literal 7 | 8 | from lunchable import TransactionUpdateObject 9 | from lunchable.models import ( 10 | CategoriesObject, 11 | AssetsObject, 12 | PlaidAccountObject, 13 | TransactionObject, 14 | ) 15 | from lunchable.plugins import LunchableApp 16 | from openai import OpenAI 17 | from pydantic import BaseModel 18 | from rich.console import Console, Group 19 | from rich.live import Live 20 | from rich.live_render import LiveRender 21 | from rich.panel import Panel 22 | 23 | 24 | def debit_system_prompt(categories: list[str]): 25 | return f""" 26 | You're an accounting platform, and you are trying your best to help an individual categorize her bank transactions: 27 | 28 | 1. Give a human friendly pretty payee name. Strip away transaction metadata like "Paypal", "Point of Sale", "Transfer", "MEMO", merchant code, location code, etc. Please reword and expand abbrevations if present. If you are uncertain, use "Unknown". 29 | 2. Try your best to map every merchant name to a specific category. If you are uncertain, use "Unknown". 30 | For categorizing, you MUST put them in one of these categories: 31 | ```json 32 | {json.dumps(categories)} 33 | ``` 34 | 3. For each transaction, please give an array of two reusable matching substrings to categorize transactions from the same brand/company/person and of the same flow direction(inflow/outflow) automatically. Even if you are uncertain about the category, you MUST try your best to give general matching patterns: 35 | a. For the first substring, isolate the payee name and REMOVE any potential ID CODE. 36 | b. For the second substring, include the GENERAL type of the transaction ONLY IF it’s helpful to decide the FLOW direction. MUST NOT include alliance info like VISA, MASTERCARD, INTERAC. MUST NOT include card type like DEBIT, CREDIT. 37 | For each matching substring, include separators from the INPUT at the START or the END. This is to make your matching substring NOT BEING a substring of OTHER potential merchants. DO NOT add any additional separators if not present in the input. 38 | MUST NOT generate regex or glob pattern. Just plain text substrings. Two substrings should NOT OVERLAP. 39 | 40 | Please strictly adhere to these rules and follow a clear path of thinking, justifying your decisions. Use natural language to spell out your thoughts in detail until you are ready to give a final response. 41 | 42 | Your final answer MUST be A JSON ARRAY with three fields in each element: "name", "category" and "matchers”. NO markdown notation for your final answer. 43 | 44 | Wrap your thinking process with "...". Wrap your final answer in "...". No other type of block is allowed in response. 45 | """ 46 | 47 | 48 | def credit_system_prompt(categories: list[str]): 49 | return f""" 50 | You're an accounting platform, and you are trying your best to help an individual categorize her bank transactions: 51 | 52 | 1. Give a human friendly pretty payee name. Strip away transaction metadata like "Paypal", "Point of Sale", "Transfer", "MEMO", merchant code, location code, etc. Please reword and expand abbrevations if present. If you are uncertain, use "Unknown". 53 | 2. Try your best to map every merchant name to a specific category. If you are uncertain, use "Unknown". 54 | For categorizing, you MUST put them in one of these categories: 55 | ```json 56 | {json.dumps(categories)} 57 | ``` 58 | 3. For each transaction, please give an array of one reusable matching substrings to categorize transactions from the same brand/company/person and of the same flow direction(inflow/outflow) automatically. Even if you are uncertain about the category, you MUST try your best to give general matching patterns: Isolate the payee name and REMOVE any potential ID CODE. 59 | For each matching substring, include separators from the INPUT at the START or the END. This is to make your matching substring NOT BEING a substring of OTHER potential merchants. DO NOT add any additional separators if not present in the input. 60 | MUST NOT generate regex or glob pattern. Just plain text substrings. 61 | 62 | Please strictly adhere to these rules and follow a clear path of thinking, justifying your decisions. Use natural language to spell out your thoughts in detail until you are ready to give a final response. 63 | 64 | Your final answer MUST be A JSON ARRAY with three fields in each element: "name", "category" and "matchers”. NO markdown notation for your final answer. 65 | 66 | Wrap your thinking process with "...". Wrap your final answer in "...". No other type of block is allowed in response. 67 | """ 68 | 69 | 70 | input_header = """ 71 | This is the input: 72 | ```json 73 | """ 74 | input_footer = """ 75 | ``` 76 | 77 | Let’s think step by step. 78 | """ 79 | 80 | 81 | class Rule(BaseModel): 82 | name: str 83 | category: str 84 | matchers: list[str] 85 | 86 | 87 | class AutoLunchApp(LunchableApp): 88 | """ 89 | Automatically categorize uncategorized transactions. 90 | """ 91 | 92 | lunchable_models = [CategoriesObject, PlaidAccountObject, AssetsObject] 93 | account: Optional[PlaidAccountObject | AssetsObject] 94 | since: Optional[datetime.datetime] 95 | until: Optional[datetime.datetime] 96 | gpt: OpenAI 97 | excluded: set[str] 98 | console: Console 99 | rules: list[Rule] 100 | mode: Literal["debit", "credit"] 101 | 102 | def __init__(self, openai_api_key: str, access_token: str | None = None) -> None: 103 | super().__init__(access_token=access_token) 104 | self.gpt = OpenAI(api_key=openai_api_key) 105 | self.account = None 106 | self.excluded = set() 107 | self.console = Console() 108 | self.mode = "debit" 109 | 110 | def set_account(self, account: PlaidAccountObject | AssetsObject): 111 | """ 112 | Set the accounts for the application. 113 | """ 114 | self.account = account 115 | 116 | def set_mode(self, mode: Literal["debit", "credit"]): 117 | """ 118 | Set the mode for the application. 119 | """ 120 | self.mode = mode 121 | 122 | def set_since_until( 123 | self, since: Optional[datetime.datetime], until: Optional[datetime.datetime] 124 | ): 125 | """ 126 | Set since and until dates for the application. 127 | """ 128 | self.since = since 129 | self.until = until 130 | 131 | def add_rules(self, rules: list[Rule]): 132 | self.rules.extend(rules) 133 | 134 | def refresh_txns(self): 135 | match self.account: 136 | case PlaidAccountObject(id=id): 137 | self.refresh_transactions( 138 | plaid_account_id=id, 139 | category_id=None, 140 | start_date=self.since, 141 | end_date=self.until, 142 | ) 143 | case AssetsObject(id=id): 144 | self.refresh_transactions( 145 | asset_id=id, 146 | category_id=None, 147 | start_date=self.since, 148 | end_date=self.until, 149 | ) 150 | 151 | def apply_rules_to_txns(self): 152 | for txn in filter( 153 | lambda txn: txn.payee not in self.excluded and txn.category_id is None, 154 | self.data.transactions_list, 155 | ): 156 | matched = False 157 | for rule in self.rules: 158 | if all(matcher in txn.payee for matcher in rule.matchers): 159 | try: 160 | category = ( 161 | next( 162 | filter( 163 | lambda c: c.name == rule.category, 164 | self.data.categories_list, 165 | ) 166 | ) 167 | if rule.category != "Unknown" 168 | else None 169 | ) 170 | except StopIteration: 171 | continue # category disappeared, skip 172 | category_id = category.id if category is not None else None 173 | self.lunch.update_transaction( 174 | txn.id, 175 | TransactionUpdateObject( 176 | category_id=category_id, 177 | payee=rule.name if rule.name != "Unknown" else None, 178 | ), 179 | ) 180 | matched = True 181 | break 182 | if not matched: 183 | yield txn 184 | 185 | def suggest_rules(self, count: int) -> typing.OrderedDict[str, Rule]: 186 | """ 187 | Sample unclassified transactions from LunchMoney. 188 | """ 189 | with self.console.status("Fetching transactions..."): 190 | self.refresh_txns() 191 | 192 | # sampled_txns: list[TransactionObject] = list(itertools.islice( 193 | # filter(lambda txn: txn.id in self.excluded_id or txn.category_id is None, self.data.transactions_list), 194 | # count)) 195 | # TODO use self.data.transactions_list and separate apply with suggest after rule endpoint gets stablized 196 | sampled_txns: list[TransactionObject] = list( 197 | itertools.islice(self.apply_rules_to_txns(), count) 198 | ) 199 | if not sampled_txns: 200 | return OrderedDict() 201 | 202 | txn_payee = list(set(txn.payee for txn in sampled_txns)) 203 | 204 | gpt_text = LiveRender("") 205 | group = Group( 206 | Panel(gpt_text, height=5), self.console.status("Generating suggestions...") 207 | ) 208 | 209 | resp_text = "" 210 | with Live(group, console=self.console, refresh_per_second=10, transient=True): 211 | stream = self.gpt.responses.create( 212 | model="gpt-4o", 213 | instructions=debit_system_prompt( 214 | [category.name for category in self.data.categories_list] 215 | ) 216 | if self.mode == "debit" 217 | else credit_system_prompt( 218 | [category.name for category in self.data.categories_list] 219 | ), 220 | input=f"{input_header}{json.dumps(txn_payee)}{input_footer}", 221 | stream=True, 222 | ) 223 | 224 | def update_text(s: str): 225 | text = gpt_text.renderable + s 226 | text = "\n".join(text.split("\n")[-5:]) 227 | gpt_text.set_renderable(text) 228 | 229 | for event in stream: 230 | if event.type == "response.output_text.delta": 231 | update_text(event.delta) 232 | elif event.type == "response.output_text.done": 233 | resp_text = event.text.strip() 234 | break 235 | elif event.type == "response.refusal.delta": 236 | update_text(event.delta) 237 | elif event.type == "response.refusal.done": 238 | resp_text = event.text.strip() 239 | break 240 | 241 | if not resp_text.startswith(""): 242 | resp_text = "" + resp_text 243 | 244 | answer_start = resp_text.find("") 245 | answer_end = resp_text.find("") 246 | if answer_start == -1 or answer_end == -1: 247 | raise ValueError("No answer found in the response") 248 | 249 | answer_text = resp_text[answer_start + 8 : answer_end].strip() 250 | raw_rules = json.loads(answer_text) 251 | rules = [Rule(**rule) for rule in raw_rules] 252 | 253 | for idx, rule in enumerate(rules): 254 | if rule.name == "Unknown" or rule.category == "Unknown": 255 | self.excluded.add(txn_payee[idx]) 256 | 257 | return OrderedDict( 258 | (txn_payee[idx], rule) 259 | for idx, rule in enumerate(rules) 260 | if rule.name != "Unknown" or rule.category != "Unknown" 261 | ) 262 | -------------------------------------------------------------------------------- /pdm.lock: -------------------------------------------------------------------------------- 1 | # This file is @generated by PDM. 2 | # It is not intended for manual editing. 3 | 4 | [metadata] 5 | groups = ["default"] 6 | strategy = ["inherit_metadata"] 7 | lock_version = "4.5.0" 8 | content_hash = "sha256:73d2f8b1ca8d7fa7afc4ef89e771338c49c4f86ca7a3f669ec3d151eb9f8d072" 9 | 10 | [[metadata.targets]] 11 | requires_python = ">=3.10" 12 | 13 | [[package]] 14 | name = "annotated-types" 15 | version = "0.7.0" 16 | requires_python = ">=3.8" 17 | summary = "Reusable constraint types to use with typing.Annotated" 18 | groups = ["default"] 19 | dependencies = [ 20 | "typing-extensions>=4.0.0; python_version < \"3.9\"", 21 | ] 22 | files = [ 23 | {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, 24 | {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, 25 | ] 26 | 27 | [[package]] 28 | name = "anyio" 29 | version = "4.9.0" 30 | requires_python = ">=3.9" 31 | summary = "High level compatibility layer for multiple asynchronous event loop implementations" 32 | groups = ["default"] 33 | dependencies = [ 34 | "exceptiongroup>=1.0.2; python_version < \"3.11\"", 35 | "idna>=2.8", 36 | "sniffio>=1.1", 37 | "typing-extensions>=4.5; python_version < \"3.13\"", 38 | ] 39 | files = [ 40 | {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, 41 | {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, 42 | ] 43 | 44 | [[package]] 45 | name = "certifi" 46 | version = "2025.1.31" 47 | requires_python = ">=3.6" 48 | summary = "Python package for providing Mozilla's CA Bundle." 49 | groups = ["default"] 50 | files = [ 51 | {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, 52 | {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, 53 | ] 54 | 55 | [[package]] 56 | name = "click" 57 | version = "8.1.8" 58 | requires_python = ">=3.7" 59 | summary = "Composable command line interface toolkit" 60 | groups = ["default"] 61 | dependencies = [ 62 | "colorama; platform_system == \"Windows\"", 63 | "importlib-metadata; python_version < \"3.8\"", 64 | ] 65 | files = [ 66 | {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, 67 | {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, 68 | ] 69 | 70 | [[package]] 71 | name = "click-plugins" 72 | version = "1.1.1" 73 | summary = "An extension module for click to enable registering CLI commands via setuptools entry-points." 74 | groups = ["default"] 75 | dependencies = [ 76 | "click>=4.0", 77 | ] 78 | files = [ 79 | {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, 80 | {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, 81 | ] 82 | 83 | [[package]] 84 | name = "colorama" 85 | version = "0.4.6" 86 | requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 87 | summary = "Cross-platform colored terminal text." 88 | groups = ["default"] 89 | marker = "platform_system == \"Windows\"" 90 | files = [ 91 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 92 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 93 | ] 94 | 95 | [[package]] 96 | name = "copykitten" 97 | version = "1.2.2" 98 | requires_python = ">=3.8" 99 | summary = "A robust, dependency-free way to use the system clipboard in Python." 100 | groups = ["default"] 101 | files = [ 102 | {file = "copykitten-1.2.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e59b1c9cc5e8ec723b9426a92e7868cdbeed0468596ca28313cf8ba1405b7128"}, 103 | {file = "copykitten-1.2.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:8b8aeef4a24f7425031df87ced2a569ced1be89ed4f2d894793a4e3697df644f"}, 104 | {file = "copykitten-1.2.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6362c526fcb4a69b5dfdbcc4ea189a880c29fae58a0bcab558837c1d59078c44"}, 105 | {file = "copykitten-1.2.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83c8aff0971ba54d73f645c464f8e105e08042e101d246c965032cf0946566f8"}, 106 | {file = "copykitten-1.2.2-cp38-abi3-win_amd64.whl", hash = "sha256:f1783b69c02a2c073964e477dade48840e80015488d81115a6ff78998ea31969"}, 107 | {file = "copykitten-1.2.2.tar.gz", hash = "sha256:d1ee05abecca7a41605d22e1a514c762656e8d5483a9da09e2623071fca22c0a"}, 108 | ] 109 | 110 | [[package]] 111 | name = "distro" 112 | version = "1.9.0" 113 | requires_python = ">=3.6" 114 | summary = "Distro - an OS platform information API" 115 | groups = ["default"] 116 | files = [ 117 | {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, 118 | {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, 119 | ] 120 | 121 | [[package]] 122 | name = "exceptiongroup" 123 | version = "1.2.2" 124 | requires_python = ">=3.7" 125 | summary = "Backport of PEP 654 (exception groups)" 126 | groups = ["default"] 127 | marker = "python_version < \"3.11\"" 128 | files = [ 129 | {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, 130 | {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, 131 | ] 132 | 133 | [[package]] 134 | name = "fuzzyfinder" 135 | version = "2.2.0" 136 | requires_python = ">=3.8" 137 | summary = "Fuzzy Finder implemented in Python." 138 | groups = ["default"] 139 | files = [ 140 | {file = "fuzzyfinder-2.2.0-py3-none-any.whl", hash = "sha256:1c60e5225bf435646e87eec3ef3d5bd094c5d5a4e4d069d0a4a07d515e990035"}, 141 | {file = "fuzzyfinder-2.2.0.tar.gz", hash = "sha256:b7699fca41230241c5312dff624029a10e40cc7f695e44290b8f2e8e070c4bb4"}, 142 | ] 143 | 144 | [[package]] 145 | name = "h11" 146 | version = "0.14.0" 147 | requires_python = ">=3.7" 148 | summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 149 | groups = ["default"] 150 | dependencies = [ 151 | "typing-extensions; python_version < \"3.8\"", 152 | ] 153 | files = [ 154 | {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, 155 | {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, 156 | ] 157 | 158 | [[package]] 159 | name = "httpcore" 160 | version = "1.0.7" 161 | requires_python = ">=3.8" 162 | summary = "A minimal low-level HTTP client." 163 | groups = ["default"] 164 | dependencies = [ 165 | "certifi", 166 | "h11<0.15,>=0.13", 167 | ] 168 | files = [ 169 | {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, 170 | {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, 171 | ] 172 | 173 | [[package]] 174 | name = "httpx" 175 | version = "0.28.1" 176 | requires_python = ">=3.8" 177 | summary = "The next generation HTTP client." 178 | groups = ["default"] 179 | dependencies = [ 180 | "anyio", 181 | "certifi", 182 | "httpcore==1.*", 183 | "idna", 184 | ] 185 | files = [ 186 | {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, 187 | {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, 188 | ] 189 | 190 | [[package]] 191 | name = "idna" 192 | version = "3.10" 193 | requires_python = ">=3.6" 194 | summary = "Internationalized Domain Names in Applications (IDNA)" 195 | groups = ["default"] 196 | files = [ 197 | {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, 198 | {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, 199 | ] 200 | 201 | [[package]] 202 | name = "importlib-metadata" 203 | version = "8.6.1" 204 | requires_python = ">=3.9" 205 | summary = "Read metadata from Python packages" 206 | groups = ["default"] 207 | dependencies = [ 208 | "typing-extensions>=3.6.4; python_version < \"3.8\"", 209 | "zipp>=3.20", 210 | ] 211 | files = [ 212 | {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, 213 | {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, 214 | ] 215 | 216 | [[package]] 217 | name = "jiter" 218 | version = "0.9.0" 219 | requires_python = ">=3.8" 220 | summary = "Fast iterable JSON parser." 221 | groups = ["default"] 222 | files = [ 223 | {file = "jiter-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:816ec9b60fdfd1fec87da1d7ed46c66c44ffec37ab2ef7de5b147b2fce3fd5ad"}, 224 | {file = "jiter-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b1d3086f8a3ee0194ecf2008cf81286a5c3e540d977fa038ff23576c023c0ea"}, 225 | {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1339f839b91ae30b37c409bf16ccd3dc453e8b8c3ed4bd1d6a567193651a4a51"}, 226 | {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ffba79584b3b670fefae66ceb3a28822365d25b7bf811e030609a3d5b876f538"}, 227 | {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cfc7d0a8e899089d11f065e289cb5b2daf3d82fbe028f49b20d7b809193958d"}, 228 | {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e00a1a2bbfaaf237e13c3d1592356eab3e9015d7efd59359ac8b51eb56390a12"}, 229 | {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1d9870561eb26b11448854dce0ff27a9a27cb616b632468cafc938de25e9e51"}, 230 | {file = "jiter-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9872aeff3f21e437651df378cb75aeb7043e5297261222b6441a620218b58708"}, 231 | {file = "jiter-0.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1fd19112d1049bdd47f17bfbb44a2c0001061312dcf0e72765bfa8abd4aa30e5"}, 232 | {file = "jiter-0.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6ef5da104664e526836070e4a23b5f68dec1cc673b60bf1edb1bfbe8a55d0678"}, 233 | {file = "jiter-0.9.0-cp310-cp310-win32.whl", hash = "sha256:cb12e6d65ebbefe5518de819f3eda53b73187b7089040b2d17f5b39001ff31c4"}, 234 | {file = "jiter-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:c43ca669493626d8672be3b645dbb406ef25af3f4b6384cfd306da7eb2e70322"}, 235 | {file = "jiter-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6c4d99c71508912a7e556d631768dcdef43648a93660670986916b297f1c54af"}, 236 | {file = "jiter-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f60fb8ce7df529812bf6c625635a19d27f30806885139e367af93f6e734ef58"}, 237 | {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51c4e1a4f8ea84d98b7b98912aa4290ac3d1eabfde8e3c34541fae30e9d1f08b"}, 238 | {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f4c677c424dc76684fea3e7285a7a2a7493424bea89ac441045e6a1fb1d7b3b"}, 239 | {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2221176dfec87f3470b21e6abca056e6b04ce9bff72315cb0b243ca9e835a4b5"}, 240 | {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c7adb66f899ffa25e3c92bfcb593391ee1947dbdd6a9a970e0d7e713237d572"}, 241 | {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98d27330fdfb77913c1097a7aab07f38ff2259048949f499c9901700789ac15"}, 242 | {file = "jiter-0.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eda3f8cc74df66892b1d06b5d41a71670c22d95a1ca2cbab73654745ce9d0419"}, 243 | {file = "jiter-0.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd5ab5ddc11418dce28343123644a100f487eaccf1de27a459ab36d6cca31043"}, 244 | {file = "jiter-0.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42f8a68a69f047b310319ef8e2f52fdb2e7976fb3313ef27df495cf77bcad965"}, 245 | {file = "jiter-0.9.0-cp311-cp311-win32.whl", hash = "sha256:a25519efb78a42254d59326ee417d6f5161b06f5da827d94cf521fed961b1ff2"}, 246 | {file = "jiter-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:923b54afdd697dfd00d368b7ccad008cccfeb1efb4e621f32860c75e9f25edbd"}, 247 | {file = "jiter-0.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7b46249cfd6c48da28f89eb0be3f52d6fdb40ab88e2c66804f546674e539ec11"}, 248 | {file = "jiter-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:609cf3c78852f1189894383cf0b0b977665f54cb38788e3e6b941fa6d982c00e"}, 249 | {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d726a3890a54561e55a9c5faea1f7655eda7f105bd165067575ace6e65f80bb2"}, 250 | {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e89dc075c1fef8fa9be219e249f14040270dbc507df4215c324a1839522ea75"}, 251 | {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04e8ffa3c353b1bc4134f96f167a2082494351e42888dfcf06e944f2729cbe1d"}, 252 | {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:203f28a72a05ae0e129b3ed1f75f56bc419d5f91dfacd057519a8bd137b00c42"}, 253 | {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fca1a02ad60ec30bb230f65bc01f611c8608b02d269f998bc29cca8619a919dc"}, 254 | {file = "jiter-0.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:237e5cee4d5d2659aaf91bbf8ec45052cc217d9446070699441a91b386ae27dc"}, 255 | {file = "jiter-0.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:528b6b71745e7326eed73c53d4aa57e2a522242320b6f7d65b9c5af83cf49b6e"}, 256 | {file = "jiter-0.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9f48e86b57bc711eb5acdfd12b6cb580a59cc9a993f6e7dcb6d8b50522dcd50d"}, 257 | {file = "jiter-0.9.0-cp312-cp312-win32.whl", hash = "sha256:699edfde481e191d81f9cf6d2211debbfe4bd92f06410e7637dffb8dd5dfde06"}, 258 | {file = "jiter-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:099500d07b43f61d8bd780466d429c45a7b25411b334c60ca875fa775f68ccb0"}, 259 | {file = "jiter-0.9.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2764891d3f3e8b18dce2cff24949153ee30c9239da7c00f032511091ba688ff7"}, 260 | {file = "jiter-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:387b22fbfd7a62418d5212b4638026d01723761c75c1c8232a8b8c37c2f1003b"}, 261 | {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d8da8629ccae3606c61d9184970423655fb4e33d03330bcdfe52d234d32f69"}, 262 | {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1be73d8982bdc278b7b9377426a4b44ceb5c7952073dd7488e4ae96b88e1103"}, 263 | {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2228eaaaa111ec54b9e89f7481bffb3972e9059301a878d085b2b449fbbde635"}, 264 | {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11509bfecbc319459647d4ac3fd391d26fdf530dad00c13c4dadabf5b81f01a4"}, 265 | {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f22238da568be8bbd8e0650e12feeb2cfea15eda4f9fc271d3b362a4fa0604d"}, 266 | {file = "jiter-0.9.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17f5d55eb856597607562257c8e36c42bc87f16bef52ef7129b7da11afc779f3"}, 267 | {file = "jiter-0.9.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6a99bed9fbb02f5bed416d137944419a69aa4c423e44189bc49718859ea83bc5"}, 268 | {file = "jiter-0.9.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e057adb0cd1bd39606100be0eafe742de2de88c79df632955b9ab53a086b3c8d"}, 269 | {file = "jiter-0.9.0-cp313-cp313-win32.whl", hash = "sha256:f7e6850991f3940f62d387ccfa54d1a92bd4bb9f89690b53aea36b4364bcab53"}, 270 | {file = "jiter-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:c8ae3bf27cd1ac5e6e8b7a27487bf3ab5f82318211ec2e1346a5b058756361f7"}, 271 | {file = "jiter-0.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0b2827fb88dda2cbecbbc3e596ef08d69bda06c6f57930aec8e79505dc17001"}, 272 | {file = "jiter-0.9.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062b756ceb1d40b0b28f326cba26cfd575a4918415b036464a52f08632731e5a"}, 273 | {file = "jiter-0.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6f7838bc467ab7e8ef9f387bd6de195c43bad82a569c1699cb822f6609dd4cdf"}, 274 | {file = "jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893"}, 275 | ] 276 | 277 | [[package]] 278 | name = "lunchable" 279 | version = "1.4.2" 280 | requires_python = "<4,>=3.8" 281 | summary = "A simple Python SDK around the Lunch Money Developer API" 282 | groups = ["default"] 283 | dependencies = [ 284 | "click-plugins>=1.1.1", 285 | "click>=8.0.1", 286 | "httpx", 287 | "importlib-metadata>=3.6", 288 | "pydantic<3,>=2", 289 | "rich>=10.0.0", 290 | ] 291 | files = [ 292 | {file = "lunchable-1.4.2-py3-none-any.whl", hash = "sha256:c00ef439446a3b0a8345bb87a49e3ff492e23d455f5515169dc29140aadbbb71"}, 293 | {file = "lunchable-1.4.2.tar.gz", hash = "sha256:2e5e0dc2658a0f57412acc8ad333cda853368a74e7c6f57f5ecc14ae51b9cb5a"}, 294 | ] 295 | 296 | [[package]] 297 | name = "markdown-it-py" 298 | version = "3.0.0" 299 | requires_python = ">=3.8" 300 | summary = "Python port of markdown-it. Markdown parsing, done right!" 301 | groups = ["default"] 302 | dependencies = [ 303 | "mdurl~=0.1", 304 | ] 305 | files = [ 306 | {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, 307 | {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, 308 | ] 309 | 310 | [[package]] 311 | name = "mdurl" 312 | version = "0.1.2" 313 | requires_python = ">=3.7" 314 | summary = "Markdown URL utilities" 315 | groups = ["default"] 316 | files = [ 317 | {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, 318 | {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, 319 | ] 320 | 321 | [[package]] 322 | name = "openai" 323 | version = "1.68.2" 324 | requires_python = ">=3.8" 325 | summary = "The official Python library for the openai API" 326 | groups = ["default"] 327 | dependencies = [ 328 | "anyio<5,>=3.5.0", 329 | "distro<2,>=1.7.0", 330 | "httpx<1,>=0.23.0", 331 | "jiter<1,>=0.4.0", 332 | "pydantic<3,>=1.9.0", 333 | "sniffio", 334 | "tqdm>4", 335 | "typing-extensions<5,>=4.11", 336 | ] 337 | files = [ 338 | {file = "openai-1.68.2-py3-none-any.whl", hash = "sha256:24484cb5c9a33b58576fdc5acf0e5f92603024a4e39d0b99793dfa1eb14c2b36"}, 339 | {file = "openai-1.68.2.tar.gz", hash = "sha256:b720f0a95a1dbe1429c0d9bb62096a0d98057bcda82516f6e8af10284bdd5b19"}, 340 | ] 341 | 342 | [[package]] 343 | name = "prompt-toolkit" 344 | version = "3.0.50" 345 | requires_python = ">=3.8.0" 346 | summary = "Library for building powerful interactive command lines in Python" 347 | groups = ["default"] 348 | dependencies = [ 349 | "wcwidth", 350 | ] 351 | files = [ 352 | {file = "prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198"}, 353 | {file = "prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab"}, 354 | ] 355 | 356 | [[package]] 357 | name = "pydantic" 358 | version = "2.10.6" 359 | requires_python = ">=3.8" 360 | summary = "Data validation using Python type hints" 361 | groups = ["default"] 362 | dependencies = [ 363 | "annotated-types>=0.6.0", 364 | "pydantic-core==2.27.2", 365 | "typing-extensions>=4.12.2", 366 | ] 367 | files = [ 368 | {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, 369 | {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, 370 | ] 371 | 372 | [[package]] 373 | name = "pydantic-core" 374 | version = "2.27.2" 375 | requires_python = ">=3.8" 376 | summary = "Core functionality for Pydantic validation and serialization" 377 | groups = ["default"] 378 | dependencies = [ 379 | "typing-extensions!=4.7.0,>=4.6.0", 380 | ] 381 | files = [ 382 | {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, 383 | {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, 384 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, 385 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, 386 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, 387 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, 388 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, 389 | {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, 390 | {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, 391 | {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, 392 | {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, 393 | {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, 394 | {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, 395 | {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, 396 | {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, 397 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, 398 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, 399 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, 400 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, 401 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, 402 | {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, 403 | {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, 404 | {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, 405 | {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, 406 | {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, 407 | {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, 408 | {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, 409 | {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, 410 | {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, 411 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, 412 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, 413 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, 414 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, 415 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, 416 | {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, 417 | {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, 418 | {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, 419 | {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, 420 | {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, 421 | {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, 422 | {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, 423 | {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, 424 | {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, 425 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, 426 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, 427 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, 428 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, 429 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, 430 | {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, 431 | {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, 432 | {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, 433 | {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, 434 | {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, 435 | {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, 436 | {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, 437 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, 438 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, 439 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, 440 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, 441 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, 442 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, 443 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, 444 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, 445 | {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, 446 | {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, 447 | ] 448 | 449 | [[package]] 450 | name = "pygments" 451 | version = "2.19.1" 452 | requires_python = ">=3.8" 453 | summary = "Pygments is a syntax highlighting package written in Python." 454 | groups = ["default"] 455 | files = [ 456 | {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, 457 | {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, 458 | ] 459 | 460 | [[package]] 461 | name = "questionary" 462 | version = "2.1.0" 463 | requires_python = ">=3.8" 464 | summary = "Python library to build pretty command line user prompts ⭐️" 465 | groups = ["default"] 466 | dependencies = [ 467 | "prompt-toolkit<4.0,>=2.0", 468 | ] 469 | files = [ 470 | {file = "questionary-2.1.0-py3-none-any.whl", hash = "sha256:44174d237b68bc828e4878c763a9ad6790ee61990e0ae72927694ead57bab8ec"}, 471 | {file = "questionary-2.1.0.tar.gz", hash = "sha256:6302cdd645b19667d8f6e6634774e9538bfcd1aad9be287e743d96cacaf95587"}, 472 | ] 473 | 474 | [[package]] 475 | name = "rapidfuzz" 476 | version = "3.12.2" 477 | requires_python = ">=3.9" 478 | summary = "rapid fuzzy string matching" 479 | groups = ["default"] 480 | files = [ 481 | {file = "rapidfuzz-3.12.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b9a75e0385a861178adf59e86d6616cbd0d5adca7228dc9eeabf6f62cf5b0b1"}, 482 | {file = "rapidfuzz-3.12.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6906a7eb458731e3dd2495af1d0410e23a21a2a2b7ced535e6d5cd15cb69afc5"}, 483 | {file = "rapidfuzz-3.12.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4b3334a8958b689f292d5ce8a928140ac98919b51e084f04bf0c14276e4c6ba"}, 484 | {file = "rapidfuzz-3.12.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:85a54ce30345cff2c79cbcffa063f270ad1daedd0d0c3ff6e541d3c3ba4288cf"}, 485 | {file = "rapidfuzz-3.12.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acb63c5072c08058f8995404201a52fc4e1ecac105548a4d03c6c6934bda45a3"}, 486 | {file = "rapidfuzz-3.12.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5385398d390c6571f0f2a7837e6ddde0c8b912dac096dc8c87208ce9aaaa7570"}, 487 | {file = "rapidfuzz-3.12.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5032cbffa245b4beba0067f8ed17392ef2501b346ae3c1f1d14b950edf4b6115"}, 488 | {file = "rapidfuzz-3.12.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:195adbb384d89d6c55e2fd71e7fb262010f3196e459aa2f3f45f31dd7185fe72"}, 489 | {file = "rapidfuzz-3.12.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f43b773a4d4950606fb25568ecde5f25280daf8f97b87eb323e16ecd8177b328"}, 490 | {file = "rapidfuzz-3.12.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:55a43be0e0fa956a919043c19d19bd988991d15c59f179d413fe5145ed9deb43"}, 491 | {file = "rapidfuzz-3.12.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:71cf1ea16acdebe9e2fb62ee7a77f8f70e877bebcbb33b34e660af2eb6d341d9"}, 492 | {file = "rapidfuzz-3.12.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a3692d4ab36d44685f61326dca539975a4eda49b2a76f0a3df177d8a2c0de9d2"}, 493 | {file = "rapidfuzz-3.12.2-cp310-cp310-win32.whl", hash = "sha256:09227bd402caa4397ba1d6e239deea635703b042dd266a4092548661fb22b9c6"}, 494 | {file = "rapidfuzz-3.12.2-cp310-cp310-win_amd64.whl", hash = "sha256:0f05b7b95f9f87254b53fa92048367a8232c26cee7fc8665e4337268c3919def"}, 495 | {file = "rapidfuzz-3.12.2-cp310-cp310-win_arm64.whl", hash = "sha256:6938738e00d9eb6e04097b3f565097e20b0c398f9c58959a2bc64f7f6be3d9da"}, 496 | {file = "rapidfuzz-3.12.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9c4d984621ae17404c58f8d06ed8b025e167e52c0e6a511dfec83c37e9220cd"}, 497 | {file = "rapidfuzz-3.12.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9f9132c55d330f0a1d34ce6730a76805323a6250d97468a1ca766a883d6a9a25"}, 498 | {file = "rapidfuzz-3.12.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b343b6cb4b2c3dbc8d2d4c5ee915b6088e3b144ddf8305a57eaab16cf9fc74"}, 499 | {file = "rapidfuzz-3.12.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24081077b571ec4ee6d5d7ea0e49bc6830bf05b50c1005028523b9cd356209f3"}, 500 | {file = "rapidfuzz-3.12.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c988a4fc91856260355773bf9d32bebab2083d4c6df33fafeddf4330e5ae9139"}, 501 | {file = "rapidfuzz-3.12.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:780b4469ee21cf62b1b2e8ada042941fd2525e45d5fb6a6901a9798a0e41153c"}, 502 | {file = "rapidfuzz-3.12.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:edd84b0a323885493c893bad16098c5e3b3005d7caa995ae653da07373665d97"}, 503 | {file = "rapidfuzz-3.12.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efa22059c765b3d8778083805b199deaaf643db070f65426f87d274565ddf36a"}, 504 | {file = "rapidfuzz-3.12.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:095776b11bb45daf7c2973dd61cc472d7ea7f2eecfa454aef940b4675659b92f"}, 505 | {file = "rapidfuzz-3.12.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7e2574cf4aa86065600b664a1ac7b8b8499107d102ecde836aaaa403fc4f1784"}, 506 | {file = "rapidfuzz-3.12.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d5a3425a6c50fd8fbd991d8f085ddb504791dae6ef9cc3ab299fea2cb5374bef"}, 507 | {file = "rapidfuzz-3.12.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:97fb05e1ddb7b71a054040af588b0634214ee87cea87900d309fafc16fd272a4"}, 508 | {file = "rapidfuzz-3.12.2-cp311-cp311-win32.whl", hash = "sha256:b4c5a0413589aef936892fbfa94b7ff6f7dd09edf19b5a7b83896cc9d4e8c184"}, 509 | {file = "rapidfuzz-3.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:58d9ae5cf9246d102db2a2558b67fe7e73c533e5d769099747921232d88b9be2"}, 510 | {file = "rapidfuzz-3.12.2-cp311-cp311-win_arm64.whl", hash = "sha256:7635fe34246cd241c8e35eb83084e978b01b83d5ef7e5bf72a704c637f270017"}, 511 | {file = "rapidfuzz-3.12.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1d982a651253ffe8434d9934ff0c1089111d60502228464721a2a4587435e159"}, 512 | {file = "rapidfuzz-3.12.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:02e6466caa0222d5233b1f05640873671cd99549a5c5ba4c29151634a1e56080"}, 513 | {file = "rapidfuzz-3.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e956b3f053e474abae69ac693a52742109d860ac2375fe88e9387d3277f4c96c"}, 514 | {file = "rapidfuzz-3.12.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dee7d740a2d5418d4f964f39ab8d89923e6b945850db833e798a1969b19542a"}, 515 | {file = "rapidfuzz-3.12.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a057cdb0401e42c84b6516c9b1635f7aedd5e430c6e388bd5f6bcd1d6a0686bb"}, 516 | {file = "rapidfuzz-3.12.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dccf8d4fb5b86d39c581a59463c596b1d09df976da26ff04ae219604223d502f"}, 517 | {file = "rapidfuzz-3.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21d5b3793c6f5aecca595cd24164bf9d3c559e315ec684f912146fc4e769e367"}, 518 | {file = "rapidfuzz-3.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:46a616c0e13cff2de1761b011e0b14bb73b110182f009223f1453d505c9a975c"}, 519 | {file = "rapidfuzz-3.12.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:19fa5bc4301a1ee55400d4a38a8ecf9522b0391fc31e6da5f4d68513fe5c0026"}, 520 | {file = "rapidfuzz-3.12.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:544a47190a0d25971658a9365dba7095397b4ce3e897f7dd0a77ca2cf6fa984e"}, 521 | {file = "rapidfuzz-3.12.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f21af27c5e001f0ba1b88c36a0936437dfe034c452548d998891c21125eb640f"}, 522 | {file = "rapidfuzz-3.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b63170d9db00629b5b3f2862114d8d6ee19127eaba0eee43762d62a25817dbe0"}, 523 | {file = "rapidfuzz-3.12.2-cp312-cp312-win32.whl", hash = "sha256:6c7152d77b2eb6bfac7baa11f2a9c45fd5a2d848dbb310acd0953b3b789d95c9"}, 524 | {file = "rapidfuzz-3.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:1a314d170ee272ac87579f25a6cf8d16a031e1f7a7b07663434b41a1473bc501"}, 525 | {file = "rapidfuzz-3.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:d41e8231326e94fd07c4d8f424f6bed08fead6f5e6688d1e6e787f1443ae7631"}, 526 | {file = "rapidfuzz-3.12.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:941f31038dba5d3dedcfcceba81d61570ad457c873a24ceb13f4f44fcb574260"}, 527 | {file = "rapidfuzz-3.12.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fe2dfc454ee51ba168a67b1e92b72aad251e45a074972cef13340bbad2fd9438"}, 528 | {file = "rapidfuzz-3.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78fafaf7f5a48ee35ccd7928339080a0136e27cf97396de45259eca1d331b714"}, 529 | {file = "rapidfuzz-3.12.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0c7989ff32c077bb8fd53253fd6ca569d1bfebc80b17557e60750e6909ba4fe"}, 530 | {file = "rapidfuzz-3.12.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96fa00bc105caa34b6cd93dca14a29243a3a7f0c336e4dcd36348d38511e15ac"}, 531 | {file = "rapidfuzz-3.12.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bccfb30c668620c5bc3490f2dc7d7da1cca0ead5a9da8b755e2e02e2ef0dff14"}, 532 | {file = "rapidfuzz-3.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f9b0adc3d894beb51f5022f64717b6114a6fabaca83d77e93ac7675911c8cc5"}, 533 | {file = "rapidfuzz-3.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32691aa59577f42864d5535cb6225d0f47e2c7bff59cf4556e5171e96af68cc1"}, 534 | {file = "rapidfuzz-3.12.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:758b10380ad34c1f51753a070d7bb278001b5e6fcf544121c6df93170952d705"}, 535 | {file = "rapidfuzz-3.12.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:50a9c54c0147b468363119132d514c5024fbad1ed8af12bd8bd411b0119f9208"}, 536 | {file = "rapidfuzz-3.12.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e3ceb87c11d2d0fbe8559bb795b0c0604b84cfc8bb7b8720b5c16e9e31e00f41"}, 537 | {file = "rapidfuzz-3.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f7c9a003002434889255ff5676ca0f8934a478065ab5e702f75dc42639505bba"}, 538 | {file = "rapidfuzz-3.12.2-cp313-cp313-win32.whl", hash = "sha256:cf165a76870cd875567941cf861dfd361a0a6e6a56b936c5d30042ddc9def090"}, 539 | {file = "rapidfuzz-3.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:55bcc003541f5f16ec0a73bf6de758161973f9e8d75161954380738dd147f9f2"}, 540 | {file = "rapidfuzz-3.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:69f6ecdf1452139f2b947d0c169a605de578efdb72cbb2373cb0a94edca1fd34"}, 541 | {file = "rapidfuzz-3.12.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e5fd3ce849b27d063755829cda27a9dab6dbd63be3801f2a40c60ec563a4c90f"}, 542 | {file = "rapidfuzz-3.12.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:54e53662d71ed660c83c5109127c8e30b9e607884b7c45d2aff7929bbbd00589"}, 543 | {file = "rapidfuzz-3.12.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b9e43cf2213e524f3309d329f1ad8dbf658db004ed44f6ae1cd2919aa997da5"}, 544 | {file = "rapidfuzz-3.12.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29ca445e320e5a8df3bd1d75b4fa4ecfa7c681942b9ac65b55168070a1a1960e"}, 545 | {file = "rapidfuzz-3.12.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83eb7ef732c2f8533c6b5fbe69858a722c218acc3e1fc190ab6924a8af7e7e0e"}, 546 | {file = "rapidfuzz-3.12.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:648adc2dd2cf873efc23befcc6e75754e204a409dfa77efd0fea30d08f22ef9d"}, 547 | {file = "rapidfuzz-3.12.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9b1e6f48e1ffa0749261ee23a1c6462bdd0be5eac83093f4711de17a42ae78ad"}, 548 | {file = "rapidfuzz-3.12.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1ae9ded463f2ca4ba1eb762913c5f14c23d2e120739a62b7f4cc102eab32dc90"}, 549 | {file = "rapidfuzz-3.12.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dda45f47b559be72ecbce45c7f71dc7c97b9772630ab0f3286d97d2c3025ab71"}, 550 | {file = "rapidfuzz-3.12.2-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3745c6443890265513a3c8777f2de4cb897aeb906a406f97741019be8ad5bcc"}, 551 | {file = "rapidfuzz-3.12.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36d3ef4f047ed1bc96fa29289f9e67a637ddca5e4f4d3dc7cb7f50eb33ec1664"}, 552 | {file = "rapidfuzz-3.12.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:54bb69ebe5ca0bd7527357e348f16a4c0c52fe0c2fcc8a041010467dcb8385f7"}, 553 | {file = "rapidfuzz-3.12.2.tar.gz", hash = "sha256:b0ba1ccc22fff782e7152a3d3d0caca44ec4e32dc48ba01c560b8593965b5aa3"}, 554 | ] 555 | 556 | [[package]] 557 | name = "readchar" 558 | version = "4.2.1" 559 | requires_python = ">=3.8" 560 | summary = "Library to easily read single chars and key strokes" 561 | groups = ["default"] 562 | files = [ 563 | {file = "readchar-4.2.1-py3-none-any.whl", hash = "sha256:a769305cd3994bb5fa2764aa4073452dc105a4ec39068ffe6efd3c20c60acc77"}, 564 | {file = "readchar-4.2.1.tar.gz", hash = "sha256:91ce3faf07688de14d800592951e5575e9c7a3213738ed01d394dcc949b79adb"}, 565 | ] 566 | 567 | [[package]] 568 | name = "rich" 569 | version = "13.9.4" 570 | requires_python = ">=3.8.0" 571 | summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 572 | groups = ["default"] 573 | dependencies = [ 574 | "markdown-it-py>=2.2.0", 575 | "pygments<3.0.0,>=2.13.0", 576 | "typing-extensions<5.0,>=4.0.0; python_version < \"3.11\"", 577 | ] 578 | files = [ 579 | {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, 580 | {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, 581 | ] 582 | 583 | [[package]] 584 | name = "sniffio" 585 | version = "1.3.1" 586 | requires_python = ">=3.7" 587 | summary = "Sniff out which async library your code is running under" 588 | groups = ["default"] 589 | files = [ 590 | {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, 591 | {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, 592 | ] 593 | 594 | [[package]] 595 | name = "thefuzz" 596 | version = "0.22.1" 597 | requires_python = ">=3.8" 598 | summary = "Fuzzy string matching in python" 599 | groups = ["default"] 600 | dependencies = [ 601 | "rapidfuzz<4.0.0,>=3.0.0", 602 | ] 603 | files = [ 604 | {file = "thefuzz-0.22.1-py3-none-any.whl", hash = "sha256:59729b33556850b90e1093c4cf9e618af6f2e4c985df193fdf3c5b5cf02ca481"}, 605 | {file = "thefuzz-0.22.1.tar.gz", hash = "sha256:7138039a7ecf540da323792d8592ef9902b1d79eb78c147d4f20664de79f3680"}, 606 | ] 607 | 608 | [[package]] 609 | name = "tqdm" 610 | version = "4.67.1" 611 | requires_python = ">=3.7" 612 | summary = "Fast, Extensible Progress Meter" 613 | groups = ["default"] 614 | dependencies = [ 615 | "colorama; platform_system == \"Windows\"", 616 | ] 617 | files = [ 618 | {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, 619 | {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, 620 | ] 621 | 622 | [[package]] 623 | name = "typing-extensions" 624 | version = "4.12.2" 625 | requires_python = ">=3.8" 626 | summary = "Backported and Experimental Type Hints for Python 3.8+" 627 | groups = ["default"] 628 | files = [ 629 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, 630 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, 631 | ] 632 | 633 | [[package]] 634 | name = "wcwidth" 635 | version = "0.2.13" 636 | summary = "Measures the displayed width of unicode strings in a terminal" 637 | groups = ["default"] 638 | dependencies = [ 639 | "backports-functools-lru-cache>=1.2.1; python_version < \"3.2\"", 640 | ] 641 | files = [ 642 | {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, 643 | {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, 644 | ] 645 | 646 | [[package]] 647 | name = "zipp" 648 | version = "3.21.0" 649 | requires_python = ">=3.9" 650 | summary = "Backport of pathlib-compatible object wrapper for zip files" 651 | groups = ["default"] 652 | files = [ 653 | {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, 654 | {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, 655 | ] 656 | --------------------------------------------------------------------------------