├── 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 | 
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 |
--------------------------------------------------------------------------------