ectf 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ectf-0.1.0/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ .idea
13
+ .DS_Store
14
+ *.deleteme
ectf-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: ectf
3
+ Version: 0.1.0
4
+ Summary: Tools for eCTF competitors
5
+ Author-email: Ben Janis <btjanis@mitre.org>
6
+ Requires-Python: >=3.13
7
+ Requires-Dist: arrow>=1.3.0
8
+ Requires-Dist: attrs>=25.3.0
9
+ Requires-Dist: loguru>=0.7.3
10
+ Requires-Dist: pyserial>=3.5
11
+ Requires-Dist: pyyaml>=6.0.2
12
+ Requires-Dist: requests>=2.32.5
13
+ Requires-Dist: rich>=14.1.0
14
+ Requires-Dist: tqdm>=4.67.1
15
+ Requires-Dist: typer>=0.16.1
ectf-0.1.0/README.md ADDED
File without changes
@@ -0,0 +1,249 @@
1
+ #include <stdint.h>
2
+ #include <string.h>
3
+ #include "crypto.h"
4
+ #include "security.h"
5
+ #include "utils.h"
6
+
7
+ void bl_zeroize_key(uint8_t *key_buf, uint16_t len)
8
+ {
9
+ memset(key_buf, 0, len);
10
+ }
11
+
12
+ uint32_t bl_load_aes_key(uint8_t *key_buf, uint16_t len)
13
+ {
14
+ if (len != AES_KEY_LEN) {
15
+ return KEY_LOAD_BAD;
16
+ }
17
+
18
+ uint32_t count = 0;
19
+
20
+ key_buf[0] = 0x20;
21
+ count++;
22
+ key_buf[1] = 0xb8;
23
+ count++;
24
+ key_buf[2] = 0xb7;
25
+ count++;
26
+ key_buf[3] = 0x12;
27
+ count++;
28
+ key_buf[4] = 0x6c;
29
+ count++;
30
+ key_buf[5] = 0x44;
31
+ count++;
32
+ key_buf[6] = 0x36;
33
+ count++;
34
+ key_buf[7] = 0x7e;
35
+ count++;
36
+ key_buf[8] = 0x34;
37
+ count++;
38
+ key_buf[9] = 0xdb;
39
+ count++;
40
+ key_buf[10] = 0x52;
41
+ count++;
42
+ key_buf[11] = 0xd5;
43
+ count++;
44
+ key_buf[12] = 0xfe;
45
+ count++;
46
+ key_buf[13] = 0xe2;
47
+ count++;
48
+ key_buf[14] = 0xa1;
49
+ count++;
50
+ key_buf[15] = 0x58;
51
+ count++;
52
+ key_buf[16] = 0x1a;
53
+ count++;
54
+ key_buf[17] = 0xd1;
55
+ count++;
56
+ key_buf[18] = 0xa;
57
+ count++;
58
+ key_buf[19] = 0x70;
59
+ count++;
60
+ key_buf[20] = 0x98;
61
+ count++;
62
+ key_buf[21] = 0x83;
63
+ count++;
64
+ key_buf[22] = 0x93;
65
+ count++;
66
+ key_buf[23] = 0x54;
67
+ count++;
68
+ key_buf[24] = 0xfd;
69
+ count++;
70
+ key_buf[25] = 0x54;
71
+ count++;
72
+ key_buf[26] = 0xea;
73
+ count++;
74
+ key_buf[27] = 0xa6;
75
+ count++;
76
+ key_buf[28] = 0xbd;
77
+ count++;
78
+ key_buf[29] = 0xfc;
79
+ count++;
80
+ key_buf[30] = 0x5f;
81
+ count++;
82
+ key_buf[31] = 0x9;
83
+ count++;
84
+
85
+ // Make sure they aren't trying to steal our keys
86
+ bl_check_flash_canary();
87
+
88
+ // Make sure they aren't faulting!
89
+ if (count == AES_KEY_LEN) {
90
+ if (~count == ~AES_KEY_LEN) {
91
+ return KEY_LOAD_OK;
92
+ }
93
+ }
94
+
95
+ return KEY_LOAD_BAD;
96
+ }
97
+
98
+ uint32_t bl_load_verify_key(uint8_t *key_buf, uint16_t len)
99
+ {
100
+ if (len != VERIFY_KEY_LEN) {
101
+ return KEY_LOAD_BAD;
102
+ }
103
+
104
+ uint32_t count = 0;
105
+
106
+ key_buf[0] = 0x4;
107
+ count++;
108
+ key_buf[1] = 0x7d;
109
+ count++;
110
+ key_buf[2] = 0xfe;
111
+ count++;
112
+ key_buf[3] = 0x24;
113
+ count++;
114
+ key_buf[4] = 0x36;
115
+ count++;
116
+ key_buf[5] = 0xf4;
117
+ count++;
118
+ key_buf[6] = 0xd;
119
+ count++;
120
+ key_buf[7] = 0xc2;
121
+ count++;
122
+ key_buf[8] = 0xc2;
123
+ count++;
124
+ key_buf[9] = 0x9e;
125
+ count++;
126
+ key_buf[10] = 0x14;
127
+ count++;
128
+ key_buf[11] = 0xf6;
129
+ count++;
130
+ key_buf[12] = 0xf3;
131
+ count++;
132
+ key_buf[13] = 0xce;
133
+ count++;
134
+ key_buf[14] = 0x3c;
135
+ count++;
136
+ key_buf[15] = 0x3;
137
+ count++;
138
+ key_buf[16] = 0x10;
139
+ count++;
140
+ key_buf[17] = 0x63;
141
+ count++;
142
+ key_buf[18] = 0x43;
143
+ count++;
144
+ key_buf[19] = 0x9;
145
+ count++;
146
+ key_buf[20] = 0x80;
147
+ count++;
148
+ key_buf[21] = 0x32;
149
+ count++;
150
+ key_buf[22] = 0xe5;
151
+ count++;
152
+ key_buf[23] = 0x9c;
153
+ count++;
154
+ key_buf[24] = 0xf9;
155
+ count++;
156
+ key_buf[25] = 0x73;
157
+ count++;
158
+ key_buf[26] = 0xfc;
159
+ count++;
160
+ key_buf[27] = 0x8c;
161
+ count++;
162
+ key_buf[28] = 0x2c;
163
+ count++;
164
+ key_buf[29] = 0x6;
165
+ count++;
166
+ key_buf[30] = 0x64;
167
+ count++;
168
+ key_buf[31] = 0xb8;
169
+ count++;
170
+ key_buf[32] = 0x1a;
171
+ count++;
172
+ key_buf[33] = 0xbc;
173
+ count++;
174
+ key_buf[34] = 0xcf;
175
+ count++;
176
+ key_buf[35] = 0xc0;
177
+ count++;
178
+ key_buf[36] = 0xd3;
179
+ count++;
180
+ key_buf[37] = 0xc3;
181
+ count++;
182
+ key_buf[38] = 0xa0;
183
+ count++;
184
+ key_buf[39] = 0xf7;
185
+ count++;
186
+ key_buf[40] = 0xb8;
187
+ count++;
188
+ key_buf[41] = 0xd4;
189
+ count++;
190
+ key_buf[42] = 0xe7;
191
+ count++;
192
+ key_buf[43] = 0x7d;
193
+ count++;
194
+ key_buf[44] = 0x35;
195
+ count++;
196
+ key_buf[45] = 0x4c;
197
+ count++;
198
+ key_buf[46] = 0x2;
199
+ count++;
200
+ key_buf[47] = 0xa4;
201
+ count++;
202
+ key_buf[48] = 0x45;
203
+ count++;
204
+ key_buf[49] = 0x72;
205
+ count++;
206
+ key_buf[50] = 0x96;
207
+ count++;
208
+ key_buf[51] = 0xd3;
209
+ count++;
210
+ key_buf[52] = 0xcb;
211
+ count++;
212
+ key_buf[53] = 0x69;
213
+ count++;
214
+ key_buf[54] = 0x8e;
215
+ count++;
216
+ key_buf[55] = 0x3e;
217
+ count++;
218
+ key_buf[56] = 0x70;
219
+ count++;
220
+ key_buf[57] = 0x3a;
221
+ count++;
222
+ key_buf[58] = 0xe3;
223
+ count++;
224
+ key_buf[59] = 0x71;
225
+ count++;
226
+ key_buf[60] = 0x84;
227
+ count++;
228
+ key_buf[61] = 0xea;
229
+ count++;
230
+ key_buf[62] = 0x19;
231
+ count++;
232
+ key_buf[63] = 0x5d;
233
+ count++;
234
+ key_buf[64] = 0xe8;
235
+ count++;
236
+
237
+ // Make sure they aren't trying to steal our keys
238
+ bl_check_flash_canary();
239
+
240
+ // Make sure they aren't faulting!
241
+ if (count == VERIFY_KEY_LEN) {
242
+ if (~count == ~VERIFY_KEY_LEN) {
243
+ return KEY_LOAD_OK;
244
+ }
245
+ }
246
+
247
+ return KEY_LOAD_BAD;
248
+ }
249
+
@@ -0,0 +1,27 @@
1
+ #ifndef __CRYPTO_H__
2
+ #define __CRYPTO_H__
3
+
4
+ #include <stdint.h>
5
+
6
+ #define AES_KEY_LEN ((uint32_t)32)
7
+
8
+ #define AES_BLOCK_SIZE ((uint32_t)16)
9
+
10
+ #define AES_IV_LEN ((uint32_t)12)
11
+
12
+ #define HASH_LEN 32
13
+
14
+ #define VERIFY_KEY_LEN ((uint32_t)65)
15
+
16
+ #define MAX_SIG_LEN ((uint32_t)100)
17
+
18
+ #define KEY_LOAD_OK ((uint32_t)0x5a5a)
19
+ #define KEY_LOAD_BAD ((uint32_t)0x3ee3)
20
+
21
+ void bl_zeroize_key(uint8_t*, uint16_t);
22
+
23
+ uint32_t bl_load_aes_key(uint8_t *key_buf, uint16_t len);
24
+
25
+ uint32_t bl_load_verify_key(uint8_t *key_buf, uint16_t len);
26
+
27
+ #endif // __CRYPTO_H__
@@ -0,0 +1,4 @@
1
+ {
2
+ "aes_key": "20b8b7126c44367e34db52d5fee2a1581ad10a7098839354fd54eaa6bdfc5f09",
3
+ "private_value": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000323a0d33165a79682cf6172d5f830350f4c63a41d0d9df64ef2992d1ef2ab71b"
4
+ }
@@ -0,0 +1,44 @@
1
+ [project]
2
+ name = "ectf"
3
+ version = "0.1.0"
4
+ description = "Tools for eCTF competitors"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Ben Janis", email = "btjanis@mitre.org" }
8
+ ]
9
+ requires-python = ">=3.13"
10
+ dependencies = [
11
+ "arrow>=1.3.0",
12
+ "attrs>=25.3.0",
13
+ "loguru>=0.7.3",
14
+ "pyserial>=3.5",
15
+ "pyyaml>=6.0.2",
16
+ "requests>=2.32.5",
17
+ "rich>=14.1.0",
18
+ "tqdm>=4.67.1",
19
+ "typer>=0.16.1",
20
+ ]
21
+
22
+ [project.scripts]
23
+ ectf = "ectf.cli:app"
24
+
25
+ [build-system]
26
+ requires = ["hatchling"]
27
+ build-backend = "hatchling.build"
28
+
29
+ [dependency-groups]
30
+ dev = [
31
+ "isort>=6.0.1",
32
+ "pre-commit>=4.2.0",
33
+ "ruff>=0.11.8",
34
+ ]
35
+
36
+ [tool.ruff.lint]
37
+ select = ["ALL"]
38
+ ignore = ["ERA001","T201","S603","PERF401","S101","PLR0912","C901","COM812","D415","D400","D203","D213","TD002","TD003","TD004","FIX002"]
39
+
40
+ [tool.ruff.lint.flake8-annotations]
41
+ mypy-init-return = true
42
+
43
+ [tool.ruff.format]
44
+ indent-style = "space"
@@ -0,0 +1,16 @@
1
+ """Tools for interacting with MITRE's eCTF
2
+
3
+ This source file is part of an example system for MITRE's 2025 Embedded System CTF
4
+ (eCTF). This code is being provided only for educational purposes for the 2025 MITRE
5
+ eCTF competition, and may not meet MITRE standards for quality. Use this code at your
6
+ own risk!
7
+
8
+ Copyright: Copyright (c) 2025 The MITRE Corporation
9
+ """
10
+
11
+ CONFIG = {"VERBOSITY": 0}
12
+
13
+ if __name__ == "__main__":
14
+ from ectf.cli import app
15
+
16
+ app()
@@ -0,0 +1,44 @@
1
+ """CLI to interact with the eCTF API
2
+
3
+ Author: Ben Janis
4
+
5
+ This source file is part of an example system for MITRE's 2025 Embedded System CTF
6
+ (eCTF). This code is being provided only for educational purposes for the 2025 MITRE
7
+ eCTF competition, and may not meet MITRE standards for quality. Use this code at your
8
+ own risk!
9
+
10
+ Copyright: Copyright (c) 2025 The MITRE Corporation
11
+ """
12
+
13
+ import webbrowser
14
+ from typing import Annotated
15
+
16
+ import typer
17
+
18
+ from ectf import CONFIG
19
+ from ectf.console import info
20
+
21
+ app = typer.Typer(help="Interact with the eCTF hardware, design, and API")
22
+
23
+
24
+ @app.command("rules")
25
+ def rules() -> None:
26
+ """Open the eCTF rules website"""
27
+ url = "https://rules.ectf.mitre.org"
28
+ info(f"Opening eCTF rules website at {url}")
29
+ webbrowser.open_new_tab(url)
30
+
31
+
32
+ @app.callback()
33
+ def set_globals(
34
+ verbose: Annotated[
35
+ int,
36
+ typer.Option("--verbose", "-v", count=True, help="Enable debug prints"),
37
+ ] = 0,
38
+ ) -> None:
39
+ """Set the verbosity of all scripts"""
40
+ CONFIG["VERBOSE"] = verbose
41
+
42
+
43
+ if __name__ == "__main__":
44
+ app()
@@ -0,0 +1,62 @@
1
+ """Interface to print to the console
2
+
3
+ Author: Ben Janis
4
+
5
+ This source file is part of an example system for MITRE's 2025 Embedded System CTF
6
+ (eCTF). This code is being provided only for educational purposes for the 2025 MITRE
7
+ eCTF competition, and may not meet MITRE standards for quality. Use this code at your
8
+ own risk!
9
+
10
+ Copyright: Copyright (c) 2025 The MITRE Corporation
11
+ """
12
+
13
+ from typing import Any
14
+
15
+ from rich.console import Console
16
+ from rich.theme import Theme
17
+
18
+ from ectf import CONFIG
19
+
20
+ console = Console(
21
+ theme=Theme(
22
+ {
23
+ "trace": "italic white",
24
+ "debug": "italic cyan",
25
+ "success": "green",
26
+ "warning": "orange1",
27
+ "error": "bold red",
28
+ },
29
+ ),
30
+ )
31
+
32
+
33
+ def trace(msg: Any) -> None: # noqa: ANN401
34
+ """Print a debug info message"""
35
+ if CONFIG["VERBOSE"] >= 2: # noqa: PLR2004
36
+ console.print(msg, style="trace")
37
+
38
+
39
+ def debug(msg: Any) -> None: # noqa: ANN401
40
+ """Print a debug info message"""
41
+ if CONFIG["VERBOSE"] >= 1:
42
+ console.print(msg, style="debug")
43
+
44
+
45
+ def info(msg: Any) -> None: # noqa: ANN401
46
+ """Print an info message"""
47
+ console.print(msg)
48
+
49
+
50
+ def success(msg: Any) -> None: # noqa: ANN401
51
+ """Print a success message"""
52
+ console.print(msg, style="success")
53
+
54
+
55
+ def warning(msg: Any) -> None: # noqa: ANN401
56
+ """Print a warning message"""
57
+ console.print(msg, style="warning")
58
+
59
+
60
+ def error(msg: Any) -> None: # noqa: ANN401
61
+ """Print an error message"""
62
+ console.print(msg, style="error")
ectf-0.1.0/uv.lock ADDED
@@ -0,0 +1,445 @@
1
+ version = 1
2
+ requires-python = ">=3.13"
3
+
4
+ [[package]]
5
+ name = "arrow"
6
+ version = "1.3.0"
7
+ source = { registry = "https://pypi.org/simple" }
8
+ dependencies = [
9
+ { name = "python-dateutil" },
10
+ { name = "types-python-dateutil" },
11
+ ]
12
+ sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960 }
13
+ wheels = [
14
+ { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419 },
15
+ ]
16
+
17
+ [[package]]
18
+ name = "attrs"
19
+ version = "25.3.0"
20
+ source = { registry = "https://pypi.org/simple" }
21
+ sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 }
22
+ wheels = [
23
+ { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 },
24
+ ]
25
+
26
+ [[package]]
27
+ name = "certifi"
28
+ version = "2025.8.3"
29
+ source = { registry = "https://pypi.org/simple" }
30
+ sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386 }
31
+ wheels = [
32
+ { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216 },
33
+ ]
34
+
35
+ [[package]]
36
+ name = "cfgv"
37
+ version = "3.4.0"
38
+ source = { registry = "https://pypi.org/simple" }
39
+ sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 }
40
+ wheels = [
41
+ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 },
42
+ ]
43
+
44
+ [[package]]
45
+ name = "charset-normalizer"
46
+ version = "3.4.3"
47
+ source = { registry = "https://pypi.org/simple" }
48
+ sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371 }
49
+ wheels = [
50
+ { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326 },
51
+ { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008 },
52
+ { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196 },
53
+ { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819 },
54
+ { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350 },
55
+ { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644 },
56
+ { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468 },
57
+ { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187 },
58
+ { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699 },
59
+ { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580 },
60
+ { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366 },
61
+ { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342 },
62
+ { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995 },
63
+ { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640 },
64
+ { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636 },
65
+ { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939 },
66
+ { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580 },
67
+ { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870 },
68
+ { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797 },
69
+ { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224 },
70
+ { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086 },
71
+ { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400 },
72
+ { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175 },
73
+ ]
74
+
75
+ [[package]]
76
+ name = "click"
77
+ version = "8.2.1"
78
+ source = { registry = "https://pypi.org/simple" }
79
+ dependencies = [
80
+ { name = "colorama", marker = "sys_platform == 'win32'" },
81
+ ]
82
+ sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342 }
83
+ wheels = [
84
+ { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 },
85
+ ]
86
+
87
+ [[package]]
88
+ name = "colorama"
89
+ version = "0.4.6"
90
+ source = { registry = "https://pypi.org/simple" }
91
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
92
+ wheels = [
93
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
94
+ ]
95
+
96
+ [[package]]
97
+ name = "distlib"
98
+ version = "0.3.9"
99
+ source = { registry = "https://pypi.org/simple" }
100
+ sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 }
101
+ wheels = [
102
+ { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 },
103
+ ]
104
+
105
+ [[package]]
106
+ name = "ectf"
107
+ version = "0.1.0"
108
+ source = { editable = "." }
109
+ dependencies = [
110
+ { name = "arrow" },
111
+ { name = "attrs" },
112
+ { name = "loguru" },
113
+ { name = "pyserial" },
114
+ { name = "pyyaml" },
115
+ { name = "requests" },
116
+ { name = "rich" },
117
+ { name = "tqdm" },
118
+ { name = "typer" },
119
+ ]
120
+
121
+ [package.dev-dependencies]
122
+ dev = [
123
+ { name = "isort" },
124
+ { name = "pre-commit" },
125
+ { name = "ruff" },
126
+ ]
127
+
128
+ [package.metadata]
129
+ requires-dist = [
130
+ { name = "arrow", specifier = ">=1.3.0" },
131
+ { name = "attrs", specifier = ">=25.3.0" },
132
+ { name = "loguru", specifier = ">=0.7.3" },
133
+ { name = "pyserial", specifier = ">=3.5" },
134
+ { name = "pyyaml", specifier = ">=6.0.2" },
135
+ { name = "requests", specifier = ">=2.32.5" },
136
+ { name = "rich", specifier = ">=14.1.0" },
137
+ { name = "tqdm", specifier = ">=4.67.1" },
138
+ { name = "typer", specifier = ">=0.16.1" },
139
+ ]
140
+
141
+ [package.metadata.requires-dev]
142
+ dev = [
143
+ { name = "isort", specifier = ">=6.0.1" },
144
+ { name = "pre-commit", specifier = ">=4.2.0" },
145
+ { name = "ruff", specifier = ">=0.11.8" },
146
+ ]
147
+
148
+ [[package]]
149
+ name = "filelock"
150
+ version = "3.18.0"
151
+ source = { registry = "https://pypi.org/simple" }
152
+ sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 }
153
+ wheels = [
154
+ { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 },
155
+ ]
156
+
157
+ [[package]]
158
+ name = "identify"
159
+ version = "2.6.10"
160
+ source = { registry = "https://pypi.org/simple" }
161
+ sdist = { url = "https://files.pythonhosted.org/packages/0c/83/b6ea0334e2e7327084a46aaaf71f2146fc061a192d6518c0d020120cd0aa/identify-2.6.10.tar.gz", hash = "sha256:45e92fd704f3da71cc3880036633f48b4b7265fd4de2b57627cb157216eb7eb8", size = 99201 }
162
+ wheels = [
163
+ { url = "https://files.pythonhosted.org/packages/2b/d3/85feeba1d097b81a44bcffa6a0beab7b4dfffe78e82fc54978d3ac380736/identify-2.6.10-py2.py3-none-any.whl", hash = "sha256:5f34248f54136beed1a7ba6a6b5c4b6cf21ff495aac7c359e1ef831ae3b8ab25", size = 99101 },
164
+ ]
165
+
166
+ [[package]]
167
+ name = "idna"
168
+ version = "3.10"
169
+ source = { registry = "https://pypi.org/simple" }
170
+ sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
171
+ wheels = [
172
+ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
173
+ ]
174
+
175
+ [[package]]
176
+ name = "isort"
177
+ version = "6.0.1"
178
+ source = { registry = "https://pypi.org/simple" }
179
+ sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955 }
180
+ wheels = [
181
+ { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186 },
182
+ ]
183
+
184
+ [[package]]
185
+ name = "loguru"
186
+ version = "0.7.3"
187
+ source = { registry = "https://pypi.org/simple" }
188
+ dependencies = [
189
+ { name = "colorama", marker = "sys_platform == 'win32'" },
190
+ { name = "win32-setctime", marker = "sys_platform == 'win32'" },
191
+ ]
192
+ sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559 }
193
+ wheels = [
194
+ { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595 },
195
+ ]
196
+
197
+ [[package]]
198
+ name = "markdown-it-py"
199
+ version = "4.0.0"
200
+ source = { registry = "https://pypi.org/simple" }
201
+ dependencies = [
202
+ { name = "mdurl" },
203
+ ]
204
+ sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070 }
205
+ wheels = [
206
+ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321 },
207
+ ]
208
+
209
+ [[package]]
210
+ name = "mdurl"
211
+ version = "0.1.2"
212
+ source = { registry = "https://pypi.org/simple" }
213
+ sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
214
+ wheels = [
215
+ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
216
+ ]
217
+
218
+ [[package]]
219
+ name = "nodeenv"
220
+ version = "1.9.1"
221
+ source = { registry = "https://pypi.org/simple" }
222
+ sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 }
223
+ wheels = [
224
+ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 },
225
+ ]
226
+
227
+ [[package]]
228
+ name = "platformdirs"
229
+ version = "4.3.8"
230
+ source = { registry = "https://pypi.org/simple" }
231
+ sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 }
232
+ wheels = [
233
+ { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 },
234
+ ]
235
+
236
+ [[package]]
237
+ name = "pre-commit"
238
+ version = "4.2.0"
239
+ source = { registry = "https://pypi.org/simple" }
240
+ dependencies = [
241
+ { name = "cfgv" },
242
+ { name = "identify" },
243
+ { name = "nodeenv" },
244
+ { name = "pyyaml" },
245
+ { name = "virtualenv" },
246
+ ]
247
+ sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424 }
248
+ wheels = [
249
+ { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707 },
250
+ ]
251
+
252
+ [[package]]
253
+ name = "pygments"
254
+ version = "2.19.2"
255
+ source = { registry = "https://pypi.org/simple" }
256
+ sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 }
257
+ wheels = [
258
+ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 },
259
+ ]
260
+
261
+ [[package]]
262
+ name = "pyserial"
263
+ version = "3.5"
264
+ source = { registry = "https://pypi.org/simple" }
265
+ sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125 }
266
+ wheels = [
267
+ { url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585 },
268
+ ]
269
+
270
+ [[package]]
271
+ name = "python-dateutil"
272
+ version = "2.9.0.post0"
273
+ source = { registry = "https://pypi.org/simple" }
274
+ dependencies = [
275
+ { name = "six" },
276
+ ]
277
+ sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 }
278
+ wheels = [
279
+ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
280
+ ]
281
+
282
+ [[package]]
283
+ name = "pyyaml"
284
+ version = "6.0.2"
285
+ source = { registry = "https://pypi.org/simple" }
286
+ sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 }
287
+ wheels = [
288
+ { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 },
289
+ { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 },
290
+ { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 },
291
+ { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 },
292
+ { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 },
293
+ { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 },
294
+ { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 },
295
+ { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 },
296
+ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 },
297
+ ]
298
+
299
+ [[package]]
300
+ name = "requests"
301
+ version = "2.32.5"
302
+ source = { registry = "https://pypi.org/simple" }
303
+ dependencies = [
304
+ { name = "certifi" },
305
+ { name = "charset-normalizer" },
306
+ { name = "idna" },
307
+ { name = "urllib3" },
308
+ ]
309
+ sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517 }
310
+ wheels = [
311
+ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738 },
312
+ ]
313
+
314
+ [[package]]
315
+ name = "rich"
316
+ version = "14.1.0"
317
+ source = { registry = "https://pypi.org/simple" }
318
+ dependencies = [
319
+ { name = "markdown-it-py" },
320
+ { name = "pygments" },
321
+ ]
322
+ sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441 }
323
+ wheels = [
324
+ { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368 },
325
+ ]
326
+
327
+ [[package]]
328
+ name = "ruff"
329
+ version = "0.11.8"
330
+ source = { registry = "https://pypi.org/simple" }
331
+ sdist = { url = "https://files.pythonhosted.org/packages/52/f6/adcf73711f31c9f5393862b4281c875a462d9f639f4ccdf69dc368311c20/ruff-0.11.8.tar.gz", hash = "sha256:6d742d10626f9004b781f4558154bb226620a7242080e11caeffab1a40e99df8", size = 4086399 }
332
+ wheels = [
333
+ { url = "https://files.pythonhosted.org/packages/9f/60/c6aa9062fa518a9f86cb0b85248245cddcd892a125ca00441df77d79ef88/ruff-0.11.8-py3-none-linux_armv6l.whl", hash = "sha256:896a37516c594805e34020c4a7546c8f8a234b679a7716a3f08197f38913e1a3", size = 10272473 },
334
+ { url = "https://files.pythonhosted.org/packages/a0/e4/0325e50d106dc87c00695f7bcd5044c6d252ed5120ebf423773e00270f50/ruff-0.11.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ab86d22d3d721a40dd3ecbb5e86ab03b2e053bc93c700dc68d1c3346b36ce835", size = 11040862 },
335
+ { url = "https://files.pythonhosted.org/packages/e6/27/b87ea1a7be37fef0adbc7fd987abbf90b6607d96aa3fc67e2c5b858e1e53/ruff-0.11.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:258f3585057508d317610e8a412788cf726efeefa2fec4dba4001d9e6f90d46c", size = 10385273 },
336
+ { url = "https://files.pythonhosted.org/packages/d3/f7/3346161570d789045ed47a86110183f6ac3af0e94e7fd682772d89f7f1a1/ruff-0.11.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727d01702f7c30baed3fc3a34901a640001a2828c793525043c29f7614994a8c", size = 10578330 },
337
+ { url = "https://files.pythonhosted.org/packages/c6/c3/327fb950b4763c7b3784f91d3038ef10c13b2d42322d4ade5ce13a2f9edb/ruff-0.11.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3dca977cc4fc8f66e89900fa415ffe4dbc2e969da9d7a54bfca81a128c5ac219", size = 10122223 },
338
+ { url = "https://files.pythonhosted.org/packages/de/c7/ba686bce9adfeb6c61cb1bbadc17d58110fe1d602f199d79d4c880170f19/ruff-0.11.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c657fa987d60b104d2be8b052d66da0a2a88f9bd1d66b2254333e84ea2720c7f", size = 11697353 },
339
+ { url = "https://files.pythonhosted.org/packages/53/8e/a4fb4a1ddde3c59e73996bb3ac51844ff93384d533629434b1def7a336b0/ruff-0.11.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f2e74b021d0de5eceb8bd32919f6ff8a9b40ee62ed97becd44993ae5b9949474", size = 12375936 },
340
+ { url = "https://files.pythonhosted.org/packages/ad/a1/9529cb1e2936e2479a51aeb011307e7229225df9ac64ae064d91ead54571/ruff-0.11.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9b5ef39820abc0f2c62111f7045009e46b275f5b99d5e59dda113c39b7f4f38", size = 11850083 },
341
+ { url = "https://files.pythonhosted.org/packages/3e/94/8f7eac4c612673ae15a4ad2bc0ee62e03c68a2d4f458daae3de0e47c67ba/ruff-0.11.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1dba3135ca503727aa4648152c0fa67c3b1385d3dc81c75cd8a229c4b2a1458", size = 14005834 },
342
+ { url = "https://files.pythonhosted.org/packages/1e/7c/6f63b46b2be870cbf3f54c9c4154d13fac4b8827f22fa05ac835c10835b2/ruff-0.11.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f024d32e62faad0f76b2d6afd141b8c171515e4fb91ce9fd6464335c81244e5", size = 11503713 },
343
+ { url = "https://files.pythonhosted.org/packages/3a/91/57de411b544b5fe072779678986a021d87c3ee5b89551f2ca41200c5d643/ruff-0.11.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d365618d3ad747432e1ae50d61775b78c055fee5936d77fb4d92c6f559741948", size = 10457182 },
344
+ { url = "https://files.pythonhosted.org/packages/01/49/cfe73e0ce5ecdd3e6f1137bf1f1be03dcc819d1bfe5cff33deb40c5926db/ruff-0.11.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4d9aaa91035bdf612c8ee7266153bcf16005c7c7e2f5878406911c92a31633cb", size = 10101027 },
345
+ { url = "https://files.pythonhosted.org/packages/56/21/a5cfe47c62b3531675795f38a0ef1c52ff8de62eaddf370d46634391a3fb/ruff-0.11.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0eba551324733efc76116d9f3a0d52946bc2751f0cd30661564117d6fd60897c", size = 11111298 },
346
+ { url = "https://files.pythonhosted.org/packages/36/98/f76225f87e88f7cb669ae92c062b11c0a1e91f32705f829bd426f8e48b7b/ruff-0.11.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:161eb4cff5cfefdb6c9b8b3671d09f7def2f960cee33481dd898caf2bcd02304", size = 11566884 },
347
+ { url = "https://files.pythonhosted.org/packages/de/7e/fff70b02e57852fda17bd43f99dda37b9bcf3e1af3d97c5834ff48d04715/ruff-0.11.8-py3-none-win32.whl", hash = "sha256:5b18caa297a786465cc511d7f8be19226acf9c0a1127e06e736cd4e1878c3ea2", size = 10451102 },
348
+ { url = "https://files.pythonhosted.org/packages/7b/a9/eaa571eb70648c9bde3120a1d5892597de57766e376b831b06e7c1e43945/ruff-0.11.8-py3-none-win_amd64.whl", hash = "sha256:6e70d11043bef637c5617297bdedec9632af15d53ac1e1ba29c448da9341b0c4", size = 11597410 },
349
+ { url = "https://files.pythonhosted.org/packages/cd/be/f6b790d6ae98f1f32c645f8540d5c96248b72343b0a56fab3a07f2941897/ruff-0.11.8-py3-none-win_arm64.whl", hash = "sha256:304432e4c4a792e3da85b7699feb3426a0908ab98bf29df22a31b0cdd098fac2", size = 10713129 },
350
+ ]
351
+
352
+ [[package]]
353
+ name = "shellingham"
354
+ version = "1.5.4"
355
+ source = { registry = "https://pypi.org/simple" }
356
+ sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 }
357
+ wheels = [
358
+ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 },
359
+ ]
360
+
361
+ [[package]]
362
+ name = "six"
363
+ version = "1.17.0"
364
+ source = { registry = "https://pypi.org/simple" }
365
+ sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 }
366
+ wheels = [
367
+ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 },
368
+ ]
369
+
370
+ [[package]]
371
+ name = "tqdm"
372
+ version = "4.67.1"
373
+ source = { registry = "https://pypi.org/simple" }
374
+ dependencies = [
375
+ { name = "colorama", marker = "sys_platform == 'win32'" },
376
+ ]
377
+ sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 }
378
+ wheels = [
379
+ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 },
380
+ ]
381
+
382
+ [[package]]
383
+ name = "typer"
384
+ version = "0.16.1"
385
+ source = { registry = "https://pypi.org/simple" }
386
+ dependencies = [
387
+ { name = "click" },
388
+ { name = "rich" },
389
+ { name = "shellingham" },
390
+ { name = "typing-extensions" },
391
+ ]
392
+ sdist = { url = "https://files.pythonhosted.org/packages/43/78/d90f616bf5f88f8710ad067c1f8705bf7618059836ca084e5bb2a0855d75/typer-0.16.1.tar.gz", hash = "sha256:d358c65a464a7a90f338e3bb7ff0c74ac081449e53884b12ba658cbd72990614", size = 102836 }
393
+ wheels = [
394
+ { url = "https://files.pythonhosted.org/packages/2d/76/06dbe78f39b2203d2a47d5facc5df5102d0561e2807396471b5f7c5a30a1/typer-0.16.1-py3-none-any.whl", hash = "sha256:90ee01cb02d9b8395ae21ee3368421faf21fa138cb2a541ed369c08cec5237c9", size = 46397 },
395
+ ]
396
+
397
+ [[package]]
398
+ name = "types-python-dateutil"
399
+ version = "2.9.0.20250822"
400
+ source = { registry = "https://pypi.org/simple" }
401
+ sdist = { url = "https://files.pythonhosted.org/packages/0c/0a/775f8551665992204c756be326f3575abba58c4a3a52eef9909ef4536428/types_python_dateutil-2.9.0.20250822.tar.gz", hash = "sha256:84c92c34bd8e68b117bff742bc00b692a1e8531262d4507b33afcc9f7716cd53", size = 16084 }
402
+ wheels = [
403
+ { url = "https://files.pythonhosted.org/packages/ab/d9/a29dfa84363e88b053bf85a8b7f212a04f0d7343a4d24933baa45c06e08b/types_python_dateutil-2.9.0.20250822-py3-none-any.whl", hash = "sha256:849d52b737e10a6dc6621d2bd7940ec7c65fcb69e6aa2882acf4e56b2b508ddc", size = 17892 },
404
+ ]
405
+
406
+ [[package]]
407
+ name = "typing-extensions"
408
+ version = "4.15.0"
409
+ source = { registry = "https://pypi.org/simple" }
410
+ sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 }
411
+ wheels = [
412
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 },
413
+ ]
414
+
415
+ [[package]]
416
+ name = "urllib3"
417
+ version = "2.5.0"
418
+ source = { registry = "https://pypi.org/simple" }
419
+ sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185 }
420
+ wheels = [
421
+ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795 },
422
+ ]
423
+
424
+ [[package]]
425
+ name = "virtualenv"
426
+ version = "20.31.2"
427
+ source = { registry = "https://pypi.org/simple" }
428
+ dependencies = [
429
+ { name = "distlib" },
430
+ { name = "filelock" },
431
+ { name = "platformdirs" },
432
+ ]
433
+ sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316 }
434
+ wheels = [
435
+ { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982 },
436
+ ]
437
+
438
+ [[package]]
439
+ name = "win32-setctime"
440
+ version = "1.2.0"
441
+ source = { registry = "https://pypi.org/simple" }
442
+ sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867 }
443
+ wheels = [
444
+ { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083 },
445
+ ]