diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4408334 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,41 @@ +# Changelog + +All notable changes to sekft, the shell-operator SFT trainer behind the +[posix-sdc](https://huggingface.co/datasets/tiararodney/posix-sdc) experiment, +are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0] - 2026-06-18 + +First release: the training and evaluation pipeline that turns posix-sdc +trajectories into a fine-tuned shell operator. + +### Added +- `sekft-train`: LoRA / QLoRA supervised fine-tuning of a base model on + shell-operation trajectories, with an **assistant-only loss mask** derived by + token-prefix differencing — the commands and the terminal `exit` / `panic` + token are trained; the environment turns (orientation, prompts, command + output) are masked to `-100`. The render uses the tokenizer's own + `apply_chat_template`, so training matches what the serving harness sends + (train = serve), with `normalize_for_template` canonicalising trajectories for + instruct templates that have no system role and require strict user/assistant + alternation. +- Three sources of training data: a directory of raw rollout `.json` + (keep-filtered), a curated `.jsonl` corpus, or the published posix-sdc corpus + over the Hugging Face Hub (`--hub`). +- `--inspect` for mask and token statistics without training, and structured + stderr logging across every phase (`-v` / `-q`): per-trajectory and progress + lines while the corpus is tokenized, dataset accounting that warns on dropped + (over-length / empty-mask) trajectories, and the per-step training curve. +- `sekft-eval`: behavioural evaluation that drops the tuned model into held-out + scenarios with no scaffold and scores whether it operates and terminates. +- `sekft-resident`: a resident-base harness that loads the base model once and + fits several adapters without reloading, for paired / STaR-style runs. +- Packaging: the `tiararodney.sekft` namespace package with `sekft-train`, + `sekft-eval`, and `sekft-resident` console scripts; a typed (`py.typed`), + mypy-strict codebase; an optional `[gpu]` extra (torch / transformers / peft); + and a dependency on `posix-sdc[hub]`. Released under GPL-2.0. + +[1.0.0]: https://git.code.tiararodney.com/tiara/sekft/releases/tag/v1.0.0 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index cb8280d..0000000 --- a/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -# Minimal dash-in-a-box for sekft trajectory generation. -# docker build -t sekft-dash . -# -# dash as the operated shell (strict POSIX, no bashisms), busybox applets for -# the coreutils. busybox is intentionally close to minimal POSIX so trajectories -# transfer toward sek rather than encoding GNU-isms. Add `coreutils findutils -# grep sed` here if you want GNU semantics instead. -FROM alpine:3.19 -RUN apk add --no-cache dash \ - && ln -sf /usr/bin/dash /bin/dash \ - && ln -sf /usr/bin/dash /bin/sh -# /work is the default arena; provider files land at their absolute paths. -RUN mkdir -p /work -WORKDIR /work diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9efa6fb --- /dev/null +++ b/LICENSE @@ -0,0 +1,338 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Moe Ghoul, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..c83a380 --- /dev/null +++ b/Pipfile @@ -0,0 +1,37 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[[source]] +url = "https://pypi.code.tiararodney.com/root/byteb4rb1e/+simple/" +verify_ssl = true +name = "pypicodetiararodney" + +[packages] +"tiararodney.sekft" = {file = ".", editable = true} +"tiararodney.posix-sdc" = {version = "*", index = "pypicodetiararodney", extras= ["hub"]} + +[dev-packages] +tox = "*" +pytest = "*" +mypy = "*" +build = "*" +twine = "*" +setuptools-scm = "~=8.2.0" +pypi-attestations = "*" +autopep8 = "*" +"tiararodney.posix-sdc" = {ref = "develop", git = "https://git.code.tiararodney.com/tiara/posix-sdc.git", extras = ["hub"]} + +[requires] +python_version = "3" + +[scripts] +"dist" = "python3 -m build" +"dist:attestations" = "python3 -m pypi_attestations sign dist/*" +"dist:publish:tiararodney" = "python3 -m twine upload --sign --repository tiararodney dist/*" +"test" = "tox" +"test:static" = "tox run -m static" +"test:unit" = "tox run -m unit" +"test:integration" = "tox run -m integration" +"test:smoke" = "tox run -m smoke" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..a1320ce --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,1660 @@ +{ + "_meta": { + "hash": { + "sha256": "53a92dc553410ed6b1bce84a3a81fdc4eb39b52a90e8581e28ac45bde5fbdc16" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + }, + { + "name": "pypicodetiararodney", + "url": "https://pypi.code.tiararodney.com/root/byteb4rb1e/+simple/", + "verify_ssl": true + } + ] + }, + "default": { + "annotated-doc": { + "hashes": [ + "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", + "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4" + ], + "markers": "python_version >= '3.8'", + "version": "==0.0.4" + }, + "anyio": { + "hashes": [ + "sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89", + "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9" + ], + "markers": "python_version >= '3.10'", + "version": "==4.14.0" + }, + "certifi": { + "hashes": [ + "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", + "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db" + ], + "markers": "python_version >= '3.7'", + "version": "==2026.6.17" + }, + "click": { + "hashes": [ + "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", + "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96" + ], + "markers": "python_version >= '3.10'", + "version": "==8.4.1" + }, + "filelock": { + "hashes": [ + "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", + "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767" + ], + "markers": "python_version >= '3.10'", + "version": "==3.29.4" + }, + "fsspec": { + "hashes": [ + "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1", + "sha256:f5bac145310fe30e16e1471bd6840b2d990d609e872251d7e674241822abf01a" + ], + "markers": "python_version >= '3.10'", + "version": "==2026.6.0" + }, + "h11": { + "hashes": [ + "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", + "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" + ], + "markers": "python_version >= '3.8'", + "version": "==0.16.0" + }, + "hf-xet": { + "hashes": [ + "sha256:0c97106032ef70467b4f6bc2d0ccc266d7613ee076afc56516c502f87ce1c4a6", + "sha256:3474760d10e3bb6f92ff3f024fcb00c0b3e4001e9b035c7483e49a5dd17aa70f", + "sha256:4f561cbbb92f80960772059864b7fb07eae879adde1b2e781ec6f86f6ac26c59", + "sha256:51ef4500dab3764b41135ee1381a4b62ce56fc54d4c92b719b59e597d6df5bf6", + "sha256:6071d5ccb4d8d2cbd5fea5cc798da4f0ba3f44e25369591c4e89a4987050e61d", + "sha256:6208adb15d192b90e4c2ad2a27ed864359b2cb0f2494eb6d7c7f3699ac02e2bf", + "sha256:6762d89b9e3267dfd502b29b2a327b4525f33b17e7b509a78d94e2151a30ce30", + "sha256:6abd35c3221eff63836618ddfb954dcf84798603f71d8e33e3ed7b04acfdbe6e", + "sha256:6f7a04a8ad962422e225bc49fbbac99dc1806764b1f3e54dbd154bffa7593947", + "sha256:8298485c1e36e7e67cbd01eeb1376619b7af43d4f1ec245caae306f890a8a32d", + "sha256:892e3a3a3aecc12aded8b93cf4f9cd059282c7de0732f7d55026f3abdf474350", + "sha256:93d090b57b211133f6c0dab0205ef5cb6d89162979ba75a74845045cc3063b8e", + "sha256:94e761bbd266bf4c03cee73753916062665ce8365aa40ed321f45afcb934b41e", + "sha256:97f212a88d14bbf573619a74b7fecb238de77d08fc702e54dec6f78276ca3283", + "sha256:a93df2039190502835b1db8cd7e178b0b7b889fe9ab51299d5ced26e0dd879a4", + "sha256:bf67e6ed10260cef62e852789dc91ebb03f382d5bdc4b1dbeb64763ea275e7d6", + "sha256:c6b6cd08ca095058780b50b8ce4d6cbf6787bcf27841705d58a9d32246e3e47a", + "sha256:d48199c2bf4f8df0adc55d31d1368b6ec0e4d4f45bc86b08038089c23db0bed8", + "sha256:dbf48c0d02cf0b2e568944330c60d9120c272dabe013bd892d48e25bc6797577", + "sha256:e1af0de8ca6f190d4294a28b88023db64a1e2d1d719cab044baf75bec569e7a9", + "sha256:e78e4e5192ad2b674c2e1160b651cb9134db974f8ae1835bdfbfb0166b894a43", + "sha256:e7dbb40617410f432182d918e37c12303fe6700fd6aa6c5964e30a535a4461d6", + "sha256:f4ad3ebd4c32dd2b27099d69dc7b2df821e30767e46fb6ee6a0713778243b8ff", + "sha256:f61e3665892a6c8c5e765395838b8ddf36185da835253d4bc4509a81e49fb342", + "sha256:f7b3002f95d1c13e24bcb4537baa8f0eb3838957067c91bb4959bc004a6435f5" + ], + "markers": "python_version >= '3.8'", + "version": "==1.5.1" + }, + "httpcore": { + "hashes": [ + "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", + "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" + ], + "markers": "python_version >= '3.8'", + "version": "==1.0.9" + }, + "httpx": { + "hashes": [ + "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", + "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" + ], + "markers": "python_version >= '3.8'", + "version": "==0.28.1" + }, + "huggingface-hub": { + "hashes": [ + "sha256:1dc72e1f6b4d6df6b30eb72e57d00514ef453d660f04af2b87f0e67267f31ee0", + "sha256:fd771622182d40977272a923953ee3b1b13538f9f8a7f5d78398f10af0f1c0bd" + ], + "markers": "python_full_version >= '3.10.0'", + "version": "==1.19.0" + }, + "idna": { + "hashes": [ + "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", + "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848" + ], + "markers": "python_version >= '3.9'", + "version": "==3.18" + }, + "markdown-it-py": { + "hashes": [ + "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", + "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a" + ], + "markers": "python_version >= '3.10'", + "version": "==4.2.0" + }, + "mdurl": { + "hashes": [ + "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" + ], + "markers": "python_version >= '3.7'", + "version": "==0.1.2" + }, + "packaging": { + "hashes": [ + "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", + "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661" + ], + "markers": "python_version >= '3.8'", + "version": "==26.2" + }, + "pygments": { + "hashes": [ + "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", + "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176" + ], + "markers": "python_version >= '3.9'", + "version": "==2.20.0" + }, + "pyyaml": { + "hashes": [ + "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", + "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", + "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", + "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", + "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", + "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", + "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", + "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", + "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", + "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", + "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", + "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6", + "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", + "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", + "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", + "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", + "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", + "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", + "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295", + "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", + "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", + "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", + "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", + "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", + "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", + "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", + "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", + "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b", + "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", + "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", + "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", + "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", + "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369", + "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", + "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", + "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", + "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", + "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", + "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", + "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", + "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", + "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", + "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", + "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", + "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", + "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", + "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", + "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", + "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", + "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4", + "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", + "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", + "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", + "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", + "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", + "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", + "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", + "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", + "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", + "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", + "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", + "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f", + "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", + "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", + "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", + "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", + "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", + "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", + "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", + "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3", + "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", + "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", + "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0" + ], + "markers": "python_version >= '3.8'", + "version": "==6.0.3" + }, + "rich": { + "hashes": [ + "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", + "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36" + ], + "markers": "python_full_version >= '3.9.0'", + "version": "==15.0.0" + }, + "shellingham": { + "hashes": [ + "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", + "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de" + ], + "markers": "python_version >= '3.7'", + "version": "==1.5.4" + }, + "tiararodney.posix-sdc": { + "extras": [ + "hub" + ], + "hashes": [ + "sha256:b9790087a3baf3ef2976e1b7c75d27a9f8892473189354f1aae4e1f3f8f63f42", + "sha256:dbe137f67ae0b93357a8e280b2568041e95d9a2f8641e21fda5e958abc5075cb" + ], + "index": "pypicodetiararodney", + "version": "==1.2.2" + }, + "tiararodney.sekft": { + "editable": true, + "file": "." + }, + "tqdm": { + "hashes": [ + "sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482", + "sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03" + ], + "markers": "python_version >= '3.8'", + "version": "==4.68.3" + }, + "typer": { + "hashes": [ + "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", + "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc" + ], + "markers": "python_version >= '3.10'", + "version": "==0.25.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", + "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" + ], + "markers": "python_version >= '3.9'", + "version": "==4.15.0" + } + }, + "develop": { + "annotated-doc": { + "hashes": [ + "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", + "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4" + ], + "markers": "python_version >= '3.8'", + "version": "==0.0.4" + }, + "annotated-types": { + "hashes": [ + "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", + "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" + ], + "markers": "python_version >= '3.8'", + "version": "==0.7.0" + }, + "anyio": { + "hashes": [ + "sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89", + "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9" + ], + "markers": "python_version >= '3.10'", + "version": "==4.14.0" + }, + "ast-serialize": { + "hashes": [ + "sha256:061ee58bdb52341c8201a6df41182a977736bae3b7ded87ca7176ca25a8a47ab", + "sha256:0668aa9459cfa8c9c49ddd2163ebcf43088ba045ef7492af6fe22e0098303101", + "sha256:104e4a35bd7c124173c41760ef9aaea17ddb3f86c65cb643671d59afbe3ee94c", + "sha256:143a4ef63285a075871908fda3672dc21864b83a8ec3ee12304aa3e4c5387b9a", + "sha256:16db7c62ec0b8efe1d7afd283a388d8f74f2605d56032e5a37747d2de8dba027", + "sha256:1943db345233cc7194a470f13afa9c59772c0b123dea0c9414c4d4ca54369759", + "sha256:239a4c354e8d676e9d94631d1d4a64edc6b266f86ff3a5a80aedd344f342c01d", + "sha256:2782c36237c46dd1674542f2109740ea5ea485a169bf1431939ada0434e17934", + "sha256:27d51654fc240a1e87e742d353d98eb45b75f62f129086b3596ab53df2ac2a43", + "sha256:36be371028fc1675acb38a331bde160dbab7ff907fdf00b67eb6911aa106951b", + "sha256:5499e8797edff2a9186aa313ed382c6b422e798e9332d9953badcee6e69a88f2", + "sha256:5880091bfe6f4f986f22866375c2e884843e7a0b6343ae41aeea659613d879b6", + "sha256:6848f2a093fb5548751a9a09bff8fcd229e2bbeb0e3331f391b6ae6d26cd9903", + "sha256:787baedb0262cc49e8ce37cc15c00ae818e46a165a3b36f5e21ed174998104cb", + "sha256:7d1a2de9de5be04652f0ed60738356ef94f66db37924a9499fffe98dc491aa0b", + "sha256:832d4c998e0b091fd60a6d6bceee535483c4d490de9ba85003af835225719261", + "sha256:842d1c004bb466c7df036f95fabef789570541922b10976b12f5592a69cf0b38", + "sha256:8f5c14f169eb0972c0c21bada5358b23d6047c76583b005234f865b11f1fa00a", + "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", + "sha256:9cc22cf0c9be65e71cf88fda130af60d61eb4a79370ad4cfe7900d48a4aa2211", + "sha256:b0c06d760909b095cc466356dfccd05a1c7233a6ca191c020dca2c6a6f16c24c", + "sha256:b15219e9cdc9f53f6f4cb51c009203507228226148c05c5e8fe451c28b435eb3", + "sha256:b54f60c1d78767a53b67eaa663f0dfac3afe606aa07f1301572f588b73d64809", + "sha256:b725026bafa801dbd7310eb13a75f0a2e370e7e51b2cb225f9d21fcfadf919ee", + "sha256:baf5eb061eb5bccade4128ad42da33787d72f6013809cd1b590376ece8b3c937", + "sha256:be5173fb66f9b49026d9d5a2ff0fc7c7009077107c0eb285b2d60fdf1fe10bd1", + "sha256:bf683d6363edf2b39eed6b6d4fe22d34b6203867a67e27134d9e2a2680c4bc4a", + "sha256:cae65289fc456fde04af979a2be09302ef5d8ab92ef23e596d6746dc267ada27", + "sha256:cf25572c526add400f26a4750dc6ce0c3bb93fc1f75e7ae0cad4ce4f2cd5c590", + "sha256:df1c00022cbbcb064bfaa505aa9c9295362443ce5dacb459d1331d3da353f887", + "sha256:e42d729ef2be96a14efbad355093284739e3670ece3e534f82cc8832790911d9", + "sha256:f66173891548c9f2726bf27957b41cabce12fa679dc6da505ddbde4d4b3b31cf", + "sha256:f8015cd071ac1339924ee2b8098c93e00e155f30a16f40ec9816fcf84f4753f6" + ], + "markers": "python_version >= '3.7'", + "version": "==0.5.0" + }, + "autopep8": { + "hashes": [ + "sha256:89440a4f969197b69a995e4ce0661b031f455a9f776d2c5ba3dbd83466931758", + "sha256:ce8ad498672c845a0c3de2629c15b635ec2b05ef8177a6e7c91c74f3e9b51128" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==2.3.2" + }, + "build": { + "hashes": [ + "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", + "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==1.5.0" + }, + "cachetools": { + "hashes": [ + "sha256:323dc4127934744db5b54eb4924482d7edafbf9554e820d1531c2e08c0e4ef54", + "sha256:437f55a4e0c1b01a4f3077cc470e6991d47430970e36fbcb77e2be0df4fc1cd6" + ], + "markers": "python_version >= '3.10'", + "version": "==7.1.4" + }, + "certifi": { + "hashes": [ + "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", + "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db" + ], + "markers": "python_version >= '3.7'", + "version": "==2026.6.17" + }, + "cffi": { + "hashes": [ + "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", + "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", + "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", + "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", + "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", + "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", + "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", + "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", + "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", + "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", + "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", + "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", + "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", + "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", + "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", + "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", + "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", + "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", + "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", + "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", + "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", + "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", + "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", + "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", + "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", + "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", + "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", + "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", + "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", + "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", + "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", + "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", + "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", + "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", + "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", + "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", + "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", + "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", + "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", + "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", + "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", + "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", + "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", + "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", + "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", + "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", + "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", + "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", + "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", + "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", + "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", + "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", + "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", + "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", + "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", + "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", + "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", + "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", + "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", + "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", + "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", + "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", + "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", + "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", + "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", + "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", + "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", + "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", + "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", + "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", + "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", + "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", + "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", + "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", + "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", + "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", + "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", + "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", + "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", + "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", + "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", + "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", + "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", + "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf" + ], + "markers": "python_version >= '3.9'", + "version": "==2.0.0" + }, + "charset-normalizer": { + "hashes": [ + "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", + "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", + "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67", + "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", + "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", + "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", + "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", + "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444", + "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", + "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9", + "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01", + "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217", + "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", + "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", + "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", + "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83", + "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5", + "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", + "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", + "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", + "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", + "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42", + "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", + "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", + "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", + "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207", + "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", + "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734", + "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", + "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", + "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", + "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", + "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", + "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", + "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", + "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", + "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", + "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", + "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", + "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", + "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", + "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", + "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", + "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", + "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", + "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", + "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776", + "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", + "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", + "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", + "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", + "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", + "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", + "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", + "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5", + "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", + "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", + "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", + "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", + "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", + "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", + "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", + "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", + "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", + "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", + "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4", + "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545", + "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706", + "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366", + "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", + "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a", + "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", + "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", + "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", + "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", + "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", + "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", + "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", + "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319", + "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", + "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", + "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", + "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", + "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", + "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0", + "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686", + "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", + "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", + "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c", + "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", + "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", + "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60", + "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", + "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274", + "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", + "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", + "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", + "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f", + "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", + "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", + "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", + "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", + "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", + "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", + "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", + "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00", + "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", + "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3", + "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7", + "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", + "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", + "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", + "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", + "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259", + "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", + "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", + "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30", + "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", + "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", + "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24", + "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", + "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", + "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc", + "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", + "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", + "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", + "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", + "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", + "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.7" + }, + "click": { + "hashes": [ + "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", + "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96" + ], + "markers": "python_version >= '3.10'", + "version": "==8.4.1" + }, + "colorama": { + "hashes": [ + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", + "version": "==0.4.6" + }, + "cryptography": { + "hashes": [ + "sha256:08a597acce1ff37f347400087776599e2348a3a8bc53b44120e463cd274efe4a", + "sha256:09f73a725d582cef64b91281a322cd798d14a33b2b6f2b7ad9531dc336d84c02", + "sha256:0df56b056bc17c1b7d6821dfa65216e62bd232d8ab05eb3db44e71d235651471", + "sha256:0ee6ea481db1ab889cba043ec1eda17bb9c1ea79db6722f779c3667f9f70322f", + "sha256:15254441469dd6bf027039453288e2072124f8b6603563f5d759e1c9b69273fa", + "sha256:266f4ee051abb2f725b74ef8072b521ce1feacf685a3364fa6a6b45548db791a", + "sha256:2c37f2461406063b417837f5f3daab668652acd82423efcd7f0a9f04be972de1", + "sha256:32143b24adb918f078134e1e230f1eb8cc04886b92c28b5f0041aaf3e5699225", + "sha256:33842cf0888951cef5bc7ac724ab844a42044c1727b967b7f8997289a0464f92", + "sha256:3752f2dbc8f07a30aad2932c986cea495b03bb554887828225da104f732852b6", + "sha256:39489bfca54c7a1f6b297efcd8bc608ab92d16c4ca631b0cad4da46724588b24", + "sha256:3e4a1a3232eef2e6c732827d5722db29a0cc8b27af2a4d865b094cf954be9ca1", + "sha256:3fd2ca57062b241c856670b073487d2e86c4637937ca5601e48f97bf8e11fc8f", + "sha256:42fcd8e26fe555d9b3577a135f5091fefa0aa4e99129c23fb56787a1bd4ada72", + "sha256:43c5835e2cb98c8733d86f57d6fc879b613f5c3478607281c3e36daffc6dd8a6", + "sha256:48fe40804d4caa2288f24e70ca8c64c42dd826da0ad7e4f1b41b2128d679e6c8", + "sha256:4ab0a343c807bbcd90c971cd1ecf072937cd01847a9e002bef88fb47ac6be577", + "sha256:4fdc69f8e4316bcf0c8c8ec1f26f285d12e8142d88d96c876a59a03be3f6ae67", + "sha256:6184ca7b174f28d7c703f1290d4b297217c45355f77a98f67e9b7f14549ac54a", + "sha256:66fd0771e7b9c6dcd44cf1120690d2338d16d72795cf40cae2786a39eba65429", + "sha256:6b2c0c3e6ccf3ade7750f836ef3ee36eea250cc467d45c256895573ac08cc6f1", + "sha256:735824ec41b7f74a7c45fb1591349333e4c696cb6c044e5f46356e560143e4cd", + "sha256:7e234ac052af99f2700826a5c29ea99d9c1b1f80341cde62d11c8154dc8e0bd9", + "sha256:869c3b8a53bfe27147832df48b32adadf558249d50e76cb3769d40e986b13265", + "sha256:86be3b1b0b6bf09482fb50a979c508d2950ed95f5621ec77f4e385962006b83a", + "sha256:86fe77abb1bd87afb251d4d02ada7ecf53a32cee9b67d976abb2e45a13297475", + "sha256:88c852a0ae366e262e5a1744b685e6a433dc8788dd2a277e418bf4904203609d", + "sha256:8ace4507d1e6533c125f4fac754f8bb8b6a74c08e92179dabd7e16571a3efbf3", + "sha256:92a46e1d638daa264ba2971c0b0489c9409787943efae4d60ffda3d091ef832c", + "sha256:9621de99d2da096006b629979efd8ae7eb2d8b822488d0c89ee4000c306c59b1", + "sha256:9a49ca6c81417f6a5edb50375a60cccdd70fa0a91a5211829dbea74eba94d2ac", + "sha256:9bd3f92d76217892b15df84ca256c2c113d386fdda7a7d8691aeeced976507c6", + "sha256:9de21387aa95e2a895823d0745b430bed4f33503ba9ab5e0b5311f33e37d66d2", + "sha256:b024e784ad6c077ee0147b35ea9cbfc1e34e1fd4c1dcca214c2794d73a12df08", + "sha256:b4e391975f038e66432328639620a4aff2d307513b004f1ca06d6225bced815c", + "sha256:b74ca3b8e5ecdd833bf6a002ca41b4793bb27fb8f1c06ffaf2643c9e9140e31b", + "sha256:b7a2d1a937a738a881737cec135a38bb61470589b17515b9f73f571d0ae10401", + "sha256:b9a32b876490d66c8bcc9963ef220199569748434ab01a9d6aaeabf88e7f5158", + "sha256:bd81490cd5801d755cf97bb68ac191f14b708470b1c7cf4580f669b9c9264cd8", + "sha256:c1400da5e32a43253392277eac7490a60e497d810a63dd5608d71bbd7af507c9", + "sha256:d069066deead00ac7f090be101be875a06855908f7ec004c27b8fefb4acfb411", + "sha256:d5d30989c6917b478b5817902e85fddaea2261efa8648383d965381ccb9e1ac4", + "sha256:df637c05205ea7c1d7fbcbe54bbfea648a52951155f997af13d895d0ecc96991", + "sha256:e361afba8918070d376df76f408a4f67fec0ee9cff81a99e48fe9a233ef59e17", + "sha256:eb86ce1af36fe65041b6db9a8bb064ee621a7e5fded0f80d475ec243477cd242", + "sha256:f0d27a5696721ef7a672b8c810f6aded391058e0b9486e63e6d93baf765da691", + "sha256:f2ceef93cb096aa3c4cc4b5c94ca6131f9196d28c64d6111533402a9b2054d41", + "sha256:f817adc181390bd54f2f700107a7419040fb7c1bdf2fc26f36551a06a68c3345", + "sha256:fe0180af5bf9236518a087e35bf2d9a347d5f5f51e63c579d683ddff424e3d46" + ], + "markers": "python_version >= '3.9' and python_full_version not in '3.9.0, 3.9.1'", + "version": "==48.0.1" + }, + "distlib": { + "hashes": [ + "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", + "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed" + ], + "version": "==0.4.3" + }, + "dnspython": { + "hashes": [ + "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", + "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f" + ], + "markers": "python_version >= '3.10'", + "version": "==2.8.0" + }, + "docutils": { + "hashes": [ + "sha256:25d013af9bf23bc1c7b2b093dff4208166c53a94786c9e447808335ef1185fea", + "sha256:746f5060322511280a1e50eb76846ed6bf2342984b2ac04dc42caa1a8d78799e" + ], + "markers": "python_version >= '3.9'", + "version": "==0.23" + }, + "email-validator": { + "hashes": [ + "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", + "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426" + ], + "markers": "python_version >= '3.8'", + "version": "==2.3.0" + }, + "filelock": { + "hashes": [ + "sha256:10cdb3656fc44541cdf30652a93fb10ec6b05325620eb316bd26893e4201538a", + "sha256:dac1648087d5115554850d113e7dd8c83ab2d38e3435dde2d4f163847e57b767" + ], + "markers": "python_version >= '3.10'", + "version": "==3.29.4" + }, + "fsspec": { + "hashes": [ + "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1", + "sha256:f5bac145310fe30e16e1471bd6840b2d990d609e872251d7e674241822abf01a" + ], + "markers": "python_version >= '3.10'", + "version": "==2026.6.0" + }, + "h11": { + "hashes": [ + "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", + "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" + ], + "markers": "python_version >= '3.8'", + "version": "==0.16.0" + }, + "hf-xet": { + "hashes": [ + "sha256:0c97106032ef70467b4f6bc2d0ccc266d7613ee076afc56516c502f87ce1c4a6", + "sha256:3474760d10e3bb6f92ff3f024fcb00c0b3e4001e9b035c7483e49a5dd17aa70f", + "sha256:4f561cbbb92f80960772059864b7fb07eae879adde1b2e781ec6f86f6ac26c59", + "sha256:51ef4500dab3764b41135ee1381a4b62ce56fc54d4c92b719b59e597d6df5bf6", + "sha256:6071d5ccb4d8d2cbd5fea5cc798da4f0ba3f44e25369591c4e89a4987050e61d", + "sha256:6208adb15d192b90e4c2ad2a27ed864359b2cb0f2494eb6d7c7f3699ac02e2bf", + "sha256:6762d89b9e3267dfd502b29b2a327b4525f33b17e7b509a78d94e2151a30ce30", + "sha256:6abd35c3221eff63836618ddfb954dcf84798603f71d8e33e3ed7b04acfdbe6e", + "sha256:6f7a04a8ad962422e225bc49fbbac99dc1806764b1f3e54dbd154bffa7593947", + "sha256:8298485c1e36e7e67cbd01eeb1376619b7af43d4f1ec245caae306f890a8a32d", + "sha256:892e3a3a3aecc12aded8b93cf4f9cd059282c7de0732f7d55026f3abdf474350", + "sha256:93d090b57b211133f6c0dab0205ef5cb6d89162979ba75a74845045cc3063b8e", + "sha256:94e761bbd266bf4c03cee73753916062665ce8365aa40ed321f45afcb934b41e", + "sha256:97f212a88d14bbf573619a74b7fecb238de77d08fc702e54dec6f78276ca3283", + "sha256:a93df2039190502835b1db8cd7e178b0b7b889fe9ab51299d5ced26e0dd879a4", + "sha256:bf67e6ed10260cef62e852789dc91ebb03f382d5bdc4b1dbeb64763ea275e7d6", + "sha256:c6b6cd08ca095058780b50b8ce4d6cbf6787bcf27841705d58a9d32246e3e47a", + "sha256:d48199c2bf4f8df0adc55d31d1368b6ec0e4d4f45bc86b08038089c23db0bed8", + "sha256:dbf48c0d02cf0b2e568944330c60d9120c272dabe013bd892d48e25bc6797577", + "sha256:e1af0de8ca6f190d4294a28b88023db64a1e2d1d719cab044baf75bec569e7a9", + "sha256:e78e4e5192ad2b674c2e1160b651cb9134db974f8ae1835bdfbfb0166b894a43", + "sha256:e7dbb40617410f432182d918e37c12303fe6700fd6aa6c5964e30a535a4461d6", + "sha256:f4ad3ebd4c32dd2b27099d69dc7b2df821e30767e46fb6ee6a0713778243b8ff", + "sha256:f61e3665892a6c8c5e765395838b8ddf36185da835253d4bc4509a81e49fb342", + "sha256:f7b3002f95d1c13e24bcb4537baa8f0eb3838957067c91bb4959bc004a6435f5" + ], + "markers": "python_version >= '3.8'", + "version": "==1.5.1" + }, + "httpcore": { + "hashes": [ + "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", + "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" + ], + "markers": "python_version >= '3.8'", + "version": "==1.0.9" + }, + "httpx": { + "hashes": [ + "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", + "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" + ], + "markers": "python_version >= '3.8'", + "version": "==0.28.1" + }, + "huggingface-hub": { + "hashes": [ + "sha256:1dc72e1f6b4d6df6b30eb72e57d00514ef453d660f04af2b87f0e67267f31ee0", + "sha256:fd771622182d40977272a923953ee3b1b13538f9f8a7f5d78398f10af0f1c0bd" + ], + "markers": "python_full_version >= '3.10.0'", + "version": "==1.19.0" + }, + "id": { + "hashes": [ + "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", + "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.1" + }, + "idna": { + "hashes": [ + "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", + "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848" + ], + "markers": "python_version >= '3.9'", + "version": "==3.18" + }, + "iniconfig": { + "hashes": [ + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + ], + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, + "jaraco.classes": { + "hashes": [ + "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", + "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790" + ], + "markers": "python_version >= '3.8'", + "version": "==3.4.0" + }, + "jaraco.context": { + "hashes": [ + "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", + "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3" + ], + "markers": "python_version >= '3.10'", + "version": "==6.1.2" + }, + "jaraco.functools": { + "hashes": [ + "sha256:3bb5665ea4a020cf78a7040e89154c77edadb3ca74f366479669c5999aa70b03", + "sha256:79ce39246eddbde4b3a03b77ea5f0f7878dc669b166a66cf3fa8e266aa3fa2f4" + ], + "markers": "python_version >= '3.10'", + "version": "==4.5.0" + }, + "jeepney": { + "hashes": [ + "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", + "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732" + ], + "markers": "python_version >= '3.7'", + "version": "==0.9.0" + }, + "keyring": { + "hashes": [ + "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", + "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b" + ], + "markers": "python_version >= '3.9'", + "version": "==25.7.0" + }, + "librt": { + "hashes": [ + "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", + "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", + "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", + "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5", + "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", + "sha256:0cad8a4d6a8ff03c9b76f9414caccd78e7cfbc8a2e12fa334d8e1d9932753783", + "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", + "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", + "sha256:0ef69ac715f3cd8e5cd252cb2aebfa72c015492aacc339d5d7bf8fef3c62c677", + "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d", + "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", + "sha256:140695816ddf3c86eb972981a26f35efd871c44b0c3aed44c8cd01749386617f", + "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412", + "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc", + "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", + "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", + "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", + "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", + "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", + "sha256:41dc19fe150b69716c8ece4f76773a9e8813fe3e35e032a58b4d46423fb8d7c0", + "sha256:461bbceede621f1ffb8839755f8663e886087ee7af16294cab7fb4d782c62eeb", + "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d", + "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", + "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", + "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be", + "sha256:4e8bd98ea9c47ae90b319a087ab28dac493f1ffbc1ecd1f28fcdbf3b7e1108d1", + "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9", + "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21", + "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", + "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b", + "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", + "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", + "sha256:624a40c4a4ad7773315c287276cd024509b2c66ff5904f504bfc08d2c70293ab", + "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c", + "sha256:6bd72d903911d995ab666dbd1871f8b1e80925a699af8063fbf50053329fb05f", + "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", + "sha256:6e94ebfcfa2d5e9926d6c3b9aa4617ffc42a845b4321fb84021b872358c82a0f", + "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", + "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7", + "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", + "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e", + "sha256:7a80a71e1fda83cc752a9141e87aae7fef279538597564d670e9ce513f286192", + "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3", + "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2", + "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", + "sha256:7e82e642ab0f7608ce2fe53d76ca2280a9ee33a1b06556142c7c6fe80a86fc33", + "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930", + "sha256:84308fc49423ce6475d1c5d1985cd69a8ca9f0325fc7d5f81bb690a3f3625d4e", + "sha256:88145c15c67731d54283d135b03244028c750cc9edc334a96a4f5950ebdb2884", + "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47", + "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73", + "sha256:92f7ff819c197fc30473190a12c2856f325ac90aabfccbeb2072d28cc2e234e3", + "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", + "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29", + "sha256:94663a21534637f0e787ec2a2a756022df6e5b7b2335a5cdd7d8e33d68a2af89", + "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44", + "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e", + "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89", + "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", + "sha256:9c028a9442a18e266955d364ce42259136e79a7ba14d773e0d778d5f70cd56f1", + "sha256:9d36a51b3d93320b686588e27123f4995804dbf1bce81df78c02fc3c6eea9280", + "sha256:9f1692105a02bcf853f355032a5fdc5494358ef83d8fd22d16de375c85cec3f5", + "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230", + "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", + "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", + "sha256:ae627397a2f351560440d872d6f7c8dbb4072e57868e7b2fc5b8b430fe489d45", + "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", + "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", + "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", + "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", + "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", + "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", + "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", + "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", + "sha256:d00f3ac06a2a8b246327f11e186a53a100a4d5c7ed52346367e5ec751d51586c", + "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", + "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", + "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", + "sha256:dc329359321b67d24efdf4bc69012b0597001649544db662c001db5a0184794c", + "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", + "sha256:dec7db73758c2b54953fd8b7fe348c45188fe26b39ee18446196edd08453a5d4", + "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", + "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", + "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5", + "sha256:f37aa505b3cf60701562eddb32df74b12a9e380c207fd8b06dd157a943ac7ea0", + "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", + "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", + "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", + "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", + "sha256:ff0fbaf5f44a21beeb0110f2ab64f45135a9536a834b79c0d1ef018f2786bbfa" + ], + "markers": "python_version >= '3.9'", + "version": "==0.11.0" + }, + "markdown-it-py": { + "hashes": [ + "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", + "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a" + ], + "markers": "python_version >= '3.10'", + "version": "==4.2.0" + }, + "mdurl": { + "hashes": [ + "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" + ], + "markers": "python_version >= '3.7'", + "version": "==0.1.2" + }, + "more-itertools": { + "hashes": [ + "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", + "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192" + ], + "markers": "python_version >= '3.10'", + "version": "==11.1.0" + }, + "mypy": { + "hashes": [ + "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", + "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666", + "sha256:11a6beb180257a805961aea9ec591bbd0bd17f1e18d35b8456d57aee5bedfedc", + "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca", + "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", + "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", + "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", + "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563", + "sha256:47cebf61abde7c088a4e27718a8b13a81655686b2e9c251f5c0915a802248166", + "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", + "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", + "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", + "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", + "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", + "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", + "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", + "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", + "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538", + "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", + "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", + "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", + "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", + "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", + "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", + "sha256:8ef78c1d306bbf9a8a12f526c44902c9c28dffd6c52c52bf6a72641ce18d3849", + "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", + "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", + "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41", + "sha256:aea7f7a8a55b459c34275fc468ada6ca7c173a5e43a68f5dbe588a563d8a06b8", + "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", + "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", + "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", + "sha256:c209a90853081ff01d01ee895cafe10f7db1474e0d95beaeef0f6c1db9119bbd", + "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", + "sha256:c989640253f0d76843e9c6c1bbf4bd48c5e85ada61bde4beb37cb3eca035685e", + "sha256:d57a90ae5e872138a425ec328edbc9b235d1934c4377881a33ec05b341acc9a8", + "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", + "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398", + "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", + "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", + "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", + "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", + "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389", + "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==2.1.0" + }, + "mypy-extensions": { + "hashes": [ + "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", + "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558" + ], + "markers": "python_version >= '3.8'", + "version": "==1.1.0" + }, + "nh3": { + "hashes": [ + "sha256:0a09f51806fd51b4fedbf9ea2b61fef388f19aef0d62fe51199d41648be14588", + "sha256:207c01801d3e9bb8ec08f08689346bdd30ce15b8bf60013a925d08b5388962a4", + "sha256:23a312224875f72cd16bde417f49071451877e29ef646a60e50fcb69407cc18a", + "sha256:2c069570b06aa848457713ad7af4a9905691291548c4466a9ad78ee95808382b", + "sha256:38748140bf76383ab7ce2dce0ad4cb663855d8fbc9098f7f3483673d09616a17", + "sha256:387abd011e81959d5a35151a11350a0795c6edeb53ebfa02d2e882dc01299263", + "sha256:3bb854485c9b33e5bb143ff3e49e577073bc6bc320f0ff8fc316dd89c0d3c101", + "sha256:45855e14ff056064fec77133bfcf7cd691838168e5e17bbef075394954dc9dc8", + "sha256:45e6a65dc88a300a2e3502cb9c8e6d1d6b831d6fba7470643333609c6aab1f30", + "sha256:488928988caad25ba14b1eb5bc74e25e21f3b5e40341d956f3ce4a8bc19460dc", + "sha256:48f45e3e914be93a596431aa143dedf1582557bf41a58153c296048d6e3798c9", + "sha256:50d401ab2d8e86d59e2126e3ab2a2f45840c405842b626d9a51624b3a33b6878", + "sha256:52d877980d7ca01dc3baf3936bf844828bc6f332962227a684ed79c18cce14c3", + "sha256:559e4c73b689e9a7aa97ac9760b1bc488038d7c1a575aa4ab5a0e19ee9630c0f", + "sha256:6ea58cc44d274c643b83547ca9654a0b1a817609b160601356f76a2b744c49ad", + "sha256:72c5bdedec27fa33de6a5326346ea8aa3fe54f6ac294d54c4b204fb66a9f1e79", + "sha256:84bdeb082544fbcb77a12c034dd77d7da0556fdc0727b787eb6214b958c15e29", + "sha256:8f85285700a18e9f3fc5bff41fe573fa84f81542ef13b48a89f9fecca0474d3b", + "sha256:acfd354e61accbe4c74f8017c6e397a776916dfe47c48643cf7fd84ade826f93", + "sha256:c357f1d042c67f135a5e6babb2b0e3b9d9224ff4a3543240f597767b01384ffd", + "sha256:c3aae321f67ae66cff2a627115f106a377d4475d10b0e13d97959a13486b9a88", + "sha256:c88605d8d468f7fc1b31e06129bc91d6c96f6c621776c9b504a0da9beac9df5f", + "sha256:de8e8621853b6470fe928c684ee0d3f39ea8086cebafe4c416486488dea7b68d", + "sha256:e49c9b564e6bcb03ecd2f057213df9a0de15a95812ac9db9600b590db23d3ae9", + "sha256:ea232933394d1d58bf7c4bb348dc4660eae6604e1ae81cd2ba6d9ed80d390f3b", + "sha256:eeedc90ed8c42c327e8e10e621ccfa314fc6cce35d5929f4297ff1cdb89667c4", + "sha256:fe3a787dc76b50de6bee54ef242f26c41dfe47654428e3e94f0fae5bb6dd2cc1" + ], + "markers": "python_version >= '3.8'", + "version": "==0.3.5" + }, + "packaging": { + "hashes": [ + "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", + "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661" + ], + "markers": "python_version >= '3.8'", + "version": "==26.2" + }, + "pathspec": { + "hashes": [ + "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", + "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189" + ], + "markers": "python_version >= '3.9'", + "version": "==1.1.1" + }, + "platformdirs": { + "hashes": [ + "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", + "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a" + ], + "markers": "python_version >= '3.10'", + "version": "==4.10.0" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pyasn1": { + "hashes": [ + "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", + "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde" + ], + "markers": "python_version >= '3.8'", + "version": "==0.6.3" + }, + "pycodestyle": { + "hashes": [ + "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", + "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d" + ], + "markers": "python_version >= '3.9'", + "version": "==2.14.0" + }, + "pycparser": { + "hashes": [ + "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", + "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992" + ], + "markers": "python_version >= '3.10'", + "version": "==3.0" + }, + "pydantic": { + "extras": [ + "email" + ], + "hashes": [ + "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", + "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6" + ], + "markers": "python_version >= '3.9'", + "version": "==2.13.4" + }, + "pydantic-core": { + "hashes": [ + "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", + "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", + "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", + "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", + "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", + "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", + "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", + "sha256:10e17cbb10a330363733efc4d7c4d0dd827ac0909b8f6a6542298fed1ea62f29", + "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", + "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", + "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", + "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", + "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", + "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", + "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", + "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", + "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", + "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", + "sha256:228ee9bae8bef5b1e97ec58302f80357c37199e0d0a99174e138d28e6957b9d9", + "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", + "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", + "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", + "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", + "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", + "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", + "sha256:3447661d99f75a3683a4cf5c87da72f2161964611864dbbeac7fbb118bb4bfc0", + "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", + "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", + "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", + "sha256:3be77f45df024d789a672ae34f8b06fb346c4f9f46ea714956660ea4862e89ac", + "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", + "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", + "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", + "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", + "sha256:432c179df7874eeb73307aad2df0755e1ae0efa61ff0ea89b93e194411ae3928", + "sha256:4a05d69cba51d852c5c3e92758653245a50c0b646ced0cf05bd793ed592839d6", + "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", + "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", + "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", + "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", + "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", + "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", + "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", + "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", + "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", + "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", + "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", + "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", + "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", + "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", + "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", + "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", + "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", + "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", + "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", + "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", + "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", + "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", + "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", + "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", + "sha256:8b9bab013d1c7a79d3501ff86d0bc9c31bf587db4551677b96bec07df78c6b15", + "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", + "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", + "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", + "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", + "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", + "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", + "sha256:91a06d2e259ecfbd8c901d70c3c507900458498142b3026a296b7de4d1322cc9", + "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", + "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", + "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", + "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", + "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", + "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", + "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", + "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", + "sha256:9f444c499b3eefd3a92e348059471ea0c3a6e303d9c1cec09fa748fd9f895201", + "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", + "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", + "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", + "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", + "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", + "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", + "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", + "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", + "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", + "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", + "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", + "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", + "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", + "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", + "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", + "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", + "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", + "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", + "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", + "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", + "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", + "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", + "sha256:d80ee3d731373b24cebbc10d689ca4ee1875caf0d5703a245db18efd4dd37fc1", + "sha256:d995260fdf4e1db774581b4900e0f832abe3c7c84996726bbc161b19c8f29e76", + "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", + "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", + "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", + "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", + "sha256:e68b7a074f65a2fd746c52a7ce6142ab7006074ac269ace0c25cd8ba171f8066", + "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", + "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", + "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", + "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", + "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", + "sha256:f13a646d65d09fbf1bc6b3a9635d30095c8e7e5cc419ff35ecc563c5fd04cd49", + "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", + "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", + "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", + "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", + "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", + "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", + "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", + "sha256:fd8b3d9fd264be37976686c7f65cd52a83f5e84f4bfd2adf9c1d469676bbb6ae" + ], + "markers": "python_version >= '3.9'", + "version": "==2.46.4" + }, + "pygments": { + "hashes": [ + "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", + "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176" + ], + "markers": "python_version >= '3.9'", + "version": "==2.20.0" + }, + "pyjwt": { + "hashes": [ + "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", + "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728" + ], + "markers": "python_version >= '3.9'", + "version": "==2.13.0" + }, + "pyopenssl": { + "hashes": [ + "sha256:4f9d971bc5298b8bc1fab282803da04bf000c755d4ad9d99b52de2569ca19a70", + "sha256:8c6fcecd1183a7fc897548dfe388b0cdb7f37e018200d8409cf33959dbe35387" + ], + "markers": "python_version >= '3.8'", + "version": "==26.2.0" + }, + "pypi-attestations": { + "hashes": [ + "sha256:278a28d741b57d62973c00d453ec9b9bb30456464d69296c6780474cd0bf098e", + "sha256:2daf3ec46ff4c7123184ec892852b4d4599b78128f01f742a44406a73200c5df" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==0.0.29" + }, + "pyproject-api": { + "hashes": [ + "sha256:c2b2726bd7aa9217b6c50b621fef5b2ae5def4d55b779c9e0694c15e0a8517ba", + "sha256:fa9e6f66c35b5017e909825d8f2b5d5482ea699d7be809d21c03bd1f7317f36a" + ], + "markers": "python_version >= '3.10'", + "version": "==1.10.1" + }, + "pyproject-hooks": { + "hashes": [ + "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", + "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913" + ], + "markers": "python_version >= '3.7'", + "version": "==1.2.0" + }, + "pytest": { + "hashes": [ + "sha256:41dd9148c08072446394cefd3d79701701335a9f4cae69ba92e39f6c7f5c061c", + "sha256:8ebb0e7888bdf2bdfc602ec51f8f62d50200af37356c74e503c79a94f5c81f32" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==9.1.0" + }, + "python-discovery": { + "hashes": [ + "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", + "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690" + ], + "markers": "python_version >= '3.8'", + "version": "==1.4.2" + }, + "pyyaml": { + "hashes": [ + "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", + "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", + "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", + "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", + "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", + "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", + "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", + "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", + "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", + "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", + "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", + "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6", + "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", + "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", + "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", + "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", + "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", + "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", + "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295", + "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", + "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", + "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", + "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", + "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", + "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", + "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", + "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", + "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b", + "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", + "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", + "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", + "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", + "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369", + "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", + "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", + "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", + "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", + "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", + "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", + "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", + "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", + "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", + "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", + "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", + "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", + "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", + "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", + "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", + "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", + "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4", + "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", + "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", + "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", + "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", + "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", + "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", + "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", + "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", + "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", + "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", + "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", + "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f", + "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", + "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", + "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", + "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", + "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", + "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", + "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", + "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3", + "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", + "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", + "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0" + ], + "markers": "python_version >= '3.8'", + "version": "==6.0.3" + }, + "readme-renderer": { + "hashes": [ + "sha256:030a8fac74904f8fba11ad1bb6964e3f76e896dc7e5e71f16af190c9056696d1", + "sha256:3385ed220117104a2bceb4a9dac8c5fdf6d1f96890d7ea2a9c7174fd5c84091f" + ], + "markers": "python_version >= '3.10'", + "version": "==45.0" + }, + "requests": { + "hashes": [ + "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", + "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed" + ], + "markers": "python_version >= '3.10'", + "version": "==2.34.2" + }, + "requests-toolbelt": { + "hashes": [ + "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", + "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.0.0" + }, + "rfc3161-client": { + "hashes": [ + "sha256:0b3920334f7334ec3bb9c319d53a5d08cd43b6883f75e2669cfd869cd264d53a", + "sha256:1671b1be16480ea54c0d36239efd0fb62c13dd572a9865a5e91fea39f1b95303", + "sha256:1be4e1133f0f7fe875629f2c358285503c1cfc79cebfbc3fb4e28b8a57d6f1a4", + "sha256:2bc9835467f6166edd6f876470484e5b294ee141add6eff6a59f5047937aaa75", + "sha256:3da328ba08139846b1ab3a03402ba8a5f3659a640dbe2cd6a18f7f342e99ba98", + "sha256:4ef4b096abe7d55b020526e39932c2721939a6c55e9a5cd3b3e77897a0942937", + "sha256:63355099d932851eac507806bb9d0937dab546a66d5857d888168799ec635f6d", + "sha256:78cdc6bde331492cb94f69328831d5c56b271012b00c6f1784c2e4b33837d585", + "sha256:8102165201c5224cf6e6634bfd68c6a39e8f800601188216f8210face4861215", + "sha256:85a1d71d1eb2c9bced2b3eb75e96f9fe49732ec2567b5dafa1dd889fff42b7fe", + "sha256:8631f7db7c1327bf87ee6a9a8681b4cd6bc2a90aae651388f29d045cd9ff1ac9", + "sha256:940e1fc95ec0ca734927a82bcb5363fa988ef1a085d238ff0c861f29c0cfb746", + "sha256:9969262fe6c08ecce39f9fe3996cf412187793834a022a643803090db5aae6b4", + "sha256:9a98e9c7ff632d9571fcea25fb70bde0e8339b86368aef67a65f6a301f125733", + "sha256:b7ad54288a49379b01b1d0d9d15167d2b7c6c7f940332ab85eeb4a6e844da8c7", + "sha256:bc379167238df32cbcc1dc9c324088559c1734331030f5293d75f4fd37b5f4f6", + "sha256:bed6ef8e194cab85f6ec5678995b6406bb568383ebb6a4301be40e7939dd28d9", + "sha256:e16ed34f6f33fd62aa3b1f83615ecf2f96e1b1f57df4e1a36570b3f895333972", + "sha256:e3caffaebf43242b000c4a6659d60eaf19c3b161ccbe05b15634a856c9ea7e61" + ], + "markers": "python_version >= '3.9'", + "version": "==1.0.6" + }, + "rfc3986": { + "hashes": [ + "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", + "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "rfc8785": { + "hashes": [ + "sha256:520d690b448ecf0703691c76e1a34a24ddcd4fc5bc41d589cb7c58ec651bcd48", + "sha256:e545841329fe0eee4f6a3b44e7034343100c12b4ec566dc06ca9735681deb4da" + ], + "markers": "python_version >= '3.8'", + "version": "==0.1.4" + }, + "rich": { + "hashes": [ + "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", + "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36" + ], + "markers": "python_full_version >= '3.9.0'", + "version": "==15.0.0" + }, + "secretstorage": { + "hashes": [ + "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", + "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be" + ], + "markers": "python_version >= '3.10'", + "version": "==3.5.0" + }, + "securesystemslib": { + "hashes": [ + "sha256:a0743a3d978cf26e98a70a57e3fbd5a18e0a74c20cabe615f6a55b02ef0272b3", + "sha256:faea87be0f9c4b4277a5fa1b54bf9bfd807be9a94ab11be6c557dc8b75c43285" + ], + "markers": "python_version ~= '3.10'", + "version": "==1.4.0" + }, + "setuptools": { + "hashes": [ + "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", + "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb" + ], + "markers": "python_version >= '3.9'", + "version": "==82.0.1" + }, + "setuptools-scm": { + "hashes": [ + "sha256:136e2b1d393d709d2bcf26f275b8dec06c48b811154167b0fd6bb002aad17d6d", + "sha256:a18396a1bc0219c974d1a74612b11f9dce0d5bd8b1dc55c65f6ac7fd609e8c28" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==8.2.0" + }, + "shellingham": { + "hashes": [ + "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", + "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de" + ], + "markers": "python_version >= '3.7'", + "version": "==1.5.4" + }, + "sigstore": { + "hashes": [ + "sha256:0f60c46c92fd4e871fbec979c9ae2aa381d7a93fbb774e49c9964550e5e16856", + "sha256:3c4b566bddfcc53e73d3adc06acf4311d72be0d907a167133abdc815a472a59b" + ], + "markers": "python_version >= '3.10'", + "version": "==4.3.0" + }, + "sigstore-models": { + "hashes": [ + "sha256:5201a68f4d7d0f8bec1e2f4378eb646b084c52609a4e31db8c385095fff68b2e", + "sha256:c766c09470c2a7e8a4a333c893f07e2001c56a3ff1757b1a246119f53169a849" + ], + "markers": "python_version >= '3.10'", + "version": "==0.0.6" + }, + "sigstore-rekor-types": { + "hashes": [ + "sha256:19aef25433218ebf9975a1e8b523cc84aaf3cd395ad39a30523b083ea7917ec5", + "sha256:b62bf38c5b1a62bc0d7fe0ee51a0709e49311d137c7880c329882a8f4b2d1d78" + ], + "markers": "python_version >= '3.8'", + "version": "==0.0.18" + }, + "tiararodney.posix-sdc": { + "extras": [ + "hub" + ], + "hashes": [ + "sha256:b9790087a3baf3ef2976e1b7c75d27a9f8892473189354f1aae4e1f3f8f63f42", + "sha256:dbe137f67ae0b93357a8e280b2568041e95d9a2f8641e21fda5e958abc5075cb" + ], + "index": "pypicodetiararodney", + "version": "==1.2.2" + }, + "tomli-w": { + "hashes": [ + "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", + "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021" + ], + "markers": "python_version >= '3.9'", + "version": "==1.2.0" + }, + "tox": { + "hashes": [ + "sha256:0678fbf26dd5b559b1ef128fa4388325920219322ebc8cc5f3497627c00f4472", + "sha256:e2084be6dfdef96ba1bed4948e6a1f73613d6952e1477be5dca45653d4c053c8" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==4.55.1" + }, + "tqdm": { + "hashes": [ + "sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482", + "sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03" + ], + "markers": "python_version >= '3.8'", + "version": "==4.68.3" + }, + "tuf": { + "hashes": [ + "sha256:572bdbdc9ff4a82278a0d4773e6100863b9b33023f27575e84ca65b486dd0d79", + "sha256:9d2e6723538e0d5a3e482b6de805fcfe64481448d5853039ba6b06ba541efd7f" + ], + "markers": "python_version >= '3.10'", + "version": "==7.0.0" + }, + "twine": { + "hashes": [ + "sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", + "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==6.2.0" + }, + "typer": { + "hashes": [ + "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", + "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc" + ], + "markers": "python_version >= '3.10'", + "version": "==0.25.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", + "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" + ], + "markers": "python_version >= '3.9'", + "version": "==4.15.0" + }, + "typing-inspection": { + "hashes": [ + "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", + "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464" + ], + "markers": "python_version >= '3.9'", + "version": "==0.4.2" + }, + "urllib3": { + "hashes": [ + "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", + "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897" + ], + "markers": "python_version >= '3.10'", + "version": "==2.7.0" + }, + "virtualenv": { + "hashes": [ + "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", + "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8" + ], + "markers": "python_version >= '3.9'", + "version": "==21.5.1" + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..22e2e67 --- /dev/null +++ b/README.md @@ -0,0 +1,79 @@ +# sekft + +Fine-tune small open models to operate a POSIX shell as a self-directed citizen: +land with **no imperative**, discover where directives live, learn the provider +from its own self-documentation, do the work, and terminate (`exit` on success, +`panic` when genuinely blocked). + +sekft is the **training half**. The dataset and the synthetic-data factory live +in [`posix-sdc`](../posix-sdc) (`tiararodney.posix-sdc`), which this package +depends on. Here live the trainer, the behavioural evaluator, and the +resident-base harness. + +## Components + +- **`sekft.sft`** (`sekft-train`) — supervised fine-tuner. Renders trajectories + with the tokenizer's own chat template and trains an **assistant-only** loss + mask (the commands plus the terminal token; environment turns masked to -100) + into a QLoRA adapter. Getting the mask wrong is the classic way to ruin a + shell-operator SFT, so it is the part tested hardest. +- **`sekft.eval`** (`sekft-eval`) — behavioural eval. Train loss says nothing + about whether the model operates the shell and leaves. This drops base + + adapter into held-out scenarios with no scaffold and reports the rates that + count: reach command-mode, terminate, checker passes. +- **`sekft.resident`** (`sekft-resident`) — resident-base harness. Loads the + 14 GB base once and keeps it hot, training and evaluating adapters without + reloading it (over OcuLink/PCIe the base transfer otherwise dominates every + run). + +## The render contract + +The render the model trains on MUST equal what it is served with. The serving +harness (ccpty) sends structured `{role, content}` messages over the OpenAI +chat-completions protocol, so the endpoint applies the **model's own chat +template**. sekft therefore renders with `apply_chat_template`, after +`normalize_for_template` canonicalises each session: a leading `system` turn is +folded into the first `user` turn and consecutive same-role turns are merged, +because instruct templates such as Mistral's have no system role and require +strict user/assistant alternation. The same canonicalisation must run +serve-side, or train and serve diverge. + +## Install + +The training paths only run on a CUDA host, so the GPU stack is an extra: + +```sh +pipenv install # editable sekft + the local editable posix-sdc +pipenv install -e '.[gpu]' # torch / transformers / peft / datasets, on the box +``` + +`pyproject.toml` declares `tiararodney.posix-sdc` abstractly; the `Pipfile` +overrides it with the local editable `../posix-sdc` for side-by-side development. + +## Use (on the GPU box) + +```sh +# fine-tune an adapter on the posix-sdc trajectories +sekft-train --data ./trajectories --base mistralai/Mistral-7B-Instruct-v0.2 \ + --out ./ckpt --load-4bit + +# inspect the assistant-only loss mask without training (runs anywhere) +sekft-train --data ./trajectories --base --inspect + +# behavioural eval on held-out scenario bundles (worlds, not trajectories) +sekft-eval --base --adapter ./ckpt --scenarios ./holdout --n 16 + +# resident loop: load the base once, cycle adapters without reloading it +sekft-resident --base --load-4bit +``` + +The eval consumes held-out **scenario bundles** from posix-sdc (it stands up and +verifies each in a fresh container), not trajectories. + +## Result + +Fine-tuning `mistralai/Mistral-7B-Instruct-v0.2` on the posix-sdc data lifted +clean termination on archetype-level held-out scenarios from **0/16 (base) to +9/16 (tuned)**: the operate-and-terminate mechanism generalised to unseen task +types, while task competence stayed archetype-local. See the experiment +[*From seed to weights*](https://blog.tiararodney.com/projects/2026/semantic-execution-kernel/experiments/from-seed-to-weights/). diff --git a/TODO b/TODO index 5860bf7..4f2c672 100644 --- a/TODO +++ b/TODO @@ -15,3 +15,237 @@ Mappings: - Module: sekft Product: sek Component: sekft + +--ISSUE +Content-Type: application/issue +ID: 1 +Type: feature +Title: Package sekft as an installable namespace package +Status: done +Priority: medium +Created: 2026-06-16 +Module: sekft +Relationships: +Description: Turn the flat trainer scripts into an installable tiararodney.sekft + namespace package: src layout, pyproject with the abstract + posix-sdc dependency and an optional gpu extra, console scripts, a + Pipfile pinning posix-sdc as a local editable override, and tox + environments. + +--ISSUE +Content-Type: application/issue +ID: 2 +Type: feature +Title: SFT trainer with chat-template render and assistant-only mask +Status: done +Priority: medium +Created: 2026-06-16 +Module: sekft +Relationships: +Description: Add the supervised fine-tuner: render trajectories through the + tokenizer's own chat template (matching serving), canonicalise + turns (fold system, merge consecutive), derive an assistant-only + loss mask by token-prefix differencing, and train a QLoRA adapter. + +--ISSUE +Content-Type: application/issue +ID: 3 +Type: feature +Title: Behavioural evaluator +Status: done +Priority: medium +Created: 2026-06-16 +Module: sekft +Relationships: +Description: Add the behavioural eval: load base plus LoRA adapter, drop it into + held-out scenarios with no scaffold, drive them through a local + operator that renders with the model's chat template, and report + reach/terminate/checker rates. + +--ISSUE +Content-Type: application/issue +ID: 4 +Type: feature +Title: Resident-base train/eval harness +Status: done +Priority: medium +Created: 2026-06-16 +Module: sekft +Relationships: +Description: Add the resident harness that loads the 14GB base once and keeps it + hot, training fresh LoRA adapters and evaluating them without + reloading the base, for the slow-OcuLink iterate loop. + +--ISSUE +Content-Type: application/issue +ID: 5 +Type: feature +Title: Pipeline overview README +Status: done +Priority: medium +Created: 2026-06-16 +Module: sekft +Relationships: +Description: Document the sekft pipeline: the trainer, evaluator, and resident + harness; how they consume the posix-sdc dataset; the render + contract; and how to run on the GPU box. + +--ISSUE +Content-Type: application/issue +ID: 6 +Type: feature +Title: Test suite: unit and smoke +Status: done +Priority: medium +Created: 2026-06-16 +Module: sekft +Relationships: +Description: Add a pytest suite: torch-free unit tests for the render + canonicalisation and assistant-only mask (fake tokenizer), and + smoke tests that the console entry points respond to --help without + the GPU stack. + +--ISSUE +Content-Type: application/issue +ID: 7 +Type: feature +Title: Add GPL-2.0 license and drop the relocated Dockerfile +Status: done +Priority: medium +Created: 2026-06-16 +Module: sekft +Relationships: +Description: License sekft under GPL-2.0 (canonical text plus pyproject + metadata) and remove the dash Dockerfile, which now lives in + posix-sdc under docker/alpine-dash. + +--ISSUE +Content-Type: application/issue +ID: 8 +Type: feature +Title: Refresh docs for the packaged trainer +Status: done +Priority: medium +Created: 2026-06-16 +Module: sekft +Relationships: +Description: The README still describes sekft as the data factory + (generate/rollout/dashdocker/taxonomy/schema), which all moved to + posix-sdc. Rewrite it as the trainer (sft/eval/resident) that + consumes posix-sdc, and update the module docstrings to + console-script invocations and the chat-template render contract. + +--ISSUE +Content-Type: application/issue +ID: 9 +Type: feature +Title: Type-check the package under mypy strict +Status: done +Priority: medium +Created: 2026-06-17 +Module: sekft +Relationships: +Description: Make the lint env honestly pass: add mypy as a dev dependency, + ignore_missing_imports for the ML libs, fully annotate + eval/resident/sft (including the inner operator callables), and + ship a py.typed marker so the Typing::Typed claim is real. + +--ISSUE +Content-Type: application/issue +ID: 10 +Type: feature +Title: structured logging for the trainer (sft) +Status: done +Priority: medium +Created: 2026-06-17 +Module: sekft +Relationships: +Description: The trainer is nearly silent: outside an example count and a save + line it prints nothing through tokenizer load, the ~14GB base-model + load, example building, and the whole training loop, and + trajectories dropped for exceeding --max-len or having an empty + loss mask vanish without a trace. Add a small shared logging setup + (_log.py, stderr so stdout stays clean for results) and a module + logger; give sekft-train -v/--verbose and -q/--quiet. Log the run + config and each phase, report dataset accounting (keepers -> + usable, with counts dropped for length / empty-mask and a warning + when any are dropped), and raise transformers' verbosity during + training so the per-step curve shows. Apply to train() and + inspect(). + +--ISSUE +Content-Type: application/issue +ID: 11 +Type: bugfix +Title: operate_rate can sum a None (eval + resident) +Status: done +Priority: medium +Created: 2026-06-17 +Module: sekft +Relationships: +Description: operate_rate computes sum(t.steps > 0 and t.meta.get('clean') for t + in rows). The 'and' yields the right operand when steps>0, so if + meta lacks the 'clean' key it yields None and sum() raises + TypeError at runtime; mypy (now that posix-sdc ships py.typed and + Trajectory is typed) flags the generator item type in eval.py:83 + and resident.py:157. Wrap the predicate in bool() so it counts + trajectories that operated and are clean, fixing both the type + error and the latent crash. + +--ISSUE +Content-Type: application/issue +ID: 12 +Type: feature +Title: load training data from a raw dir, a curated jsonl, or the Hub +Status: done +Priority: medium +Created: 2026-06-17 +Module: sekft +Relationships: +Description: iter_keepers reads only raw per-trajectory .json - one of three + input shapes the trainer should accept. Add load_turns(data, hub, + revision) that yields assistant-bearing turns from: a directory of + raw rollout .json (keep-filtered, today's iter_keepers); a curated + .jsonl corpus file (already keep-filtered, yield turns per line); + or the published corpus via posix-sdc's load_trajectories (local + data/ in a checkout, else the Hub). sekft-train gains --hub and + --revision; --data dispatches by dir-vs-.jsonl. Raw-rollout reading + stays sekft-local; curated+Hub reuse posix-sdc's loader (imported + lazily so the trainer needs neither posix-sdc nor huggingface_hub + for the raw/jsonl paths). Unit tests for the raw-dir and jsonl + dispatch. + +--ISSUE +Content-Type: application/issue +ID: 13 +Type: feature +Title: reference posix-sdc three ways for seamless multi-machine dev +Status: done +Priority: medium +Created: 2026-06-17 +Module: sekft +Relationships: +Description: Wire the posix-sdc dependency as a triplet: the abstract + posix-sdc[hub] in pyproject (so the trainer's --hub path can reach + the Hub via huggingface_hub); the published wheel from the private + index in Pipfile [packages]; the git develop branch in Pipfile + [dev-packages] for develop-time. Commit Pipfile.lock so the + dependency surface and lock land together. + +--ISSUE +Content-Type: application/issue +ID: 14 +Type: bugfix +Title: refresh Pipfile.lock against published posix-sdc 1.2.2 +Status: done +Priority: medium +Created: 2026-06-17 +Module: sekft +Relationships: +Description: The lock committed with the triplet (#13) predated the published + posix-sdc 1.2.2 wheel, so it could not pin the real [hub] closure. + Now that 1.2.2 is on the private index, re-lock: posix-sdc resolves + to ==1.2.2 from the index and the [hub] extra pulls huggingface_hub + and its transitive deps into the lock. Commit the refreshed + Pipfile.lock so the next machine installs the published wheel with + the Hub path available. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..dcbab8c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,92 @@ +[build-system] +requires = [ + "setuptools", + "wheel", + "setuptools-scm[toml]" +] +build-backend = "setuptools.build_meta" + +[project] +name = "tiararodney.sekft" +description = "Fine-tune small open models to operate a POSIX shell (sek)" +authors = [ + { name = "Tiara Rodney", email = "tiara.rodney@byteb4rb1e.me" } +] +license-files = ["LICENSE"] +readme = "README.md" +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", + "Natural Language :: English", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: System :: Shells", + "Typing :: Typed", +] +dependencies = [ + "tiararodney.posix-sdc[hub]", +] +dynamic = ["version"] +requires-python = ">=3.9" + +[project.optional-dependencies] +gpu = [ + "torch", + "transformers", + "peft", + "datasets", + "accelerate", + "bitsandbytes", + "tensorboard", +] + +[project.scripts] +sekft-train = "tiararodney.sekft.sft:main" +sekft-eval = "tiararodney.sekft.eval:main" +sekft-resident = "tiararodney.sekft.resident:main" + +[project.urls] +Git = "https://git.code.tiararodney.com/tiararodney/sekft" + +[tool.setuptools.packages.find] +where = ["src"] +namespaces = true + +[tool.setuptools.package-data] +"tiararodney.sekft" = ["py.typed"] + +[tool.pytest.ini_options] +pythonpath = ["src", "../posix-sdc/src"] +testpaths = ["tests"] +markers = [ + "pytest: integration tests runnable without external services", + "gpu: requires torch and a GPU", + "docker: requires Docker and the sekft-dash image", +] + +[tool.mypy] +strict = true +mypy_path = "src" +explicit_package_bases = true +namespace_packages = true + +[[tool.mypy.overrides]] +module = [ + "torch.*", "transformers.*", "peft.*", "datasets.*", "bitsandbytes.*", + "tiararodney.posix_sdc.*", +] +ignore_missing_imports = true + +[tool.autopep8] +max_line_length = 80 +aggressive = 3 +recursive = true + +[tool.setuptools_scm] diff --git a/src/tiararodney/sekft/__init__.py b/src/tiararodney/sekft/__init__.py new file mode 100644 index 0000000..0cb60f3 --- /dev/null +++ b/src/tiararodney/sekft/__init__.py @@ -0,0 +1,5 @@ +"""sekft: fine-tune small open models to operate a POSIX shell (sek). + +Consumes the posix-sdc dataset; the trainer, behavioural evaluator, and the +resident-base harness live here. +""" diff --git a/src/tiararodney/sekft/_log.py b/src/tiararodney/sekft/_log.py new file mode 100644 index 0000000..8cb4d4f --- /dev/null +++ b/src/tiararodney/sekft/_log.py @@ -0,0 +1,20 @@ +"""Console logging setup shared by the sekft entry points. + +Logs go to stderr so stdout stays clean for a command's actual output (metrics +JSON, a path a caller might capture). Call :func:`setup` once at the top of a +``main()``; modules then log through ``logging.getLogger("sekft.")``. +""" +from __future__ import annotations + +import logging + + +def setup(verbose: bool = False, quiet: bool = False) -> None: + """Configure root logging to stderr. ``quiet`` shows warnings and worse, + ``verbose`` adds debug; the default is info.""" + level = logging.WARNING if quiet else logging.DEBUG if verbose else logging.INFO + logging.basicConfig( + level=level, + format="%(asctime)s %(levelname)-5s %(name)s %(message)s", + datefmt="%H:%M:%S", + ) diff --git a/src/tiararodney/sekft/eval.py b/src/tiararodney/sekft/eval.py new file mode 100644 index 0000000..5d5964c --- /dev/null +++ b/src/tiararodney/sekft/eval.py @@ -0,0 +1,105 @@ +"""Behavioural eval: the metric that matters. + +Train loss says nothing about whether the model operates the shell and leaves. +This loads a fine-tuned model (base + LoRA adapter), drops it into held-out +scenarios with NO scaffold (the trained behaviour must stand on its own), and +reports the rates that count: does it reach command-mode, does it terminate, +does the checker pass. + + sekft-eval --base --adapter ./ckpt-mistral-r16 \ + --scenarios ./holdout-scenarios --n 10 + +Reuses the posix-sdc rollout loop with a *local* operator: the model renders and +generates with the same chat template it was trained on (train == eval == serve, +via ``apply_chat_template`` + ``normalize_for_template``, or the prompts go out +of distribution). Prerequisites on the box: torch + transformers + peft, the +``sekft-dash`` image, and held-out SCENARIO bundles from the posix-sdc factory +(not trajectories; the eval stands up and verifies each). +""" +from __future__ import annotations + +import argparse +import json +from collections.abc import Callable +from pathlib import Path +from typing import Any + +from tiararodney.posix_sdc.factory.dashdocker import DashDocker, available +from tiararodney.posix_sdc.factory.rollout import rollout +from tiararodney.posix_sdc.schema import Scenario + +from .sft import normalize_for_template + + +def make_local_operator(base: str, adapter: str, max_new_tokens: int = 64, + temperature: float = 0.7) -> Callable[[list[dict[str, str]]], str]: + """A ``messages -> command`` callable backed by base + LoRA adapter. + + Renders the conversation exactly as the model was trained, appends the + assistant header, generates one turn, and cuts at the first stop marker. + """ + import torch + from peft import PeftModel + from transformers import AutoModelForCausalLM, AutoTokenizer + + tok = AutoTokenizer.from_pretrained(adapter) + model = AutoModelForCausalLM.from_pretrained( + base, torch_dtype=torch.float16, device_map="auto") + model = PeftModel.from_pretrained(model, adapter) + model.eval() + + def operator(messages: list[dict[str, str]]) -> str: + msgs = normalize_for_template(messages) + ids = tok.apply_chat_template( + msgs, add_generation_prompt=True, return_tensors="pt").to(model.device) + with torch.no_grad(): + out = model.generate( + ids, max_new_tokens=max_new_tokens, + do_sample=temperature > 0, temperature=max(temperature, 1e-2), + eos_token_id=tok.eos_token_id, pad_token_id=tok.eos_token_id) + text: str = tok.decode(out[0][ids.shape[1]:], skip_special_tokens=True).strip() + return text + + return operator + + +def evaluate(base: str, adapter: str, scenarios_dir: Path, n: int, + max_steps: int, temperature: float) -> dict[str, Any]: + if not available(): + raise SystemExit("sekft-dash image unavailable; `docker build -t sekft-dash .`") + operator = make_local_operator(base, adapter, temperature=temperature) + backend = DashDocker() + rows = [] + for f in sorted(scenarios_dir.glob("*.json"))[:n]: + sc = Scenario.from_dict(json.loads(f.read_text())) + tj = rollout(sc, backend, max_steps=max_steps, temperature=temperature, + operator=operator, use_scaffold=False) + rows.append(tj) + print(f" {sc.id}: {tj.outcome} (terminal={tj.terminal} " + f"verified={tj.verified} steps={tj.steps})") + d = len(rows) or 1 + return { + "n": len(rows), + "operate_rate": round(sum(bool(t.steps > 0 and t.meta.get("clean")) for t in rows) / d, 3), + "terminate_rate": round(sum(t.terminal in ("exit", "panic") for t in rows) / d, 3), + "verified_rate": round(sum(t.verified for t in rows) / d, 3), + "clean_rate": round(sum(t.keep for t in rows) / d, 3), + } + + +def main() -> None: + ap = argparse.ArgumentParser(description="Behavioural eval of a tuned model.") + ap.add_argument("--base", required=True) + ap.add_argument("--adapter", required=True) + ap.add_argument("--scenarios", type=Path, required=True) + ap.add_argument("--n", type=int, default=10) + ap.add_argument("--max-steps", type=int, default=30) + ap.add_argument("--temperature", type=float, default=0.7) + ns = ap.parse_args() + m = evaluate(ns.base, ns.adapter, ns.scenarios, ns.n, ns.max_steps, ns.temperature) + print("\n=== behavioural metrics ===") + print(json.dumps(m, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/src/tiararodney/sekft/py.typed b/src/tiararodney/sekft/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/tiararodney/sekft/resident.py b/src/tiararodney/sekft/resident.py new file mode 100644 index 0000000..2d7bc3b --- /dev/null +++ b/src/tiararodney/sekft/resident.py @@ -0,0 +1,189 @@ +"""Resident harness: load the base ONCE, cycle adapters. + +On a slow link (OcuLink / PCIe 3.0 x4) the 14 GB base transfer dominates every +process start. This loads the base once and keeps it hot, so the +iterate-train-eval loop pays the transfer only at startup. Each ``fit`` trains a +fresh LoRA adapter on the resident base and ``unload``s it back to clean; each +``evaluate`` attaches a saved adapter for inference and unloads. + +Interactive (IPython on the GPU box) is the intended use: + + from tiararodney.sekft.resident import Resident + r = Resident("~/llm-models/mistral-7b-instruct-v0.2", load_4bit=True) + r.fit("~/sekft/trajectories", "~/sekft/ckpt-a", lora_r=16, lr=2e-4, epochs=3) + r.evaluate("~/sekft/ckpt-a", "~/sekft/holdout", n=10) + r.fit("~/sekft/trajectories", "~/sekft/ckpt-b", lora_r=32) # NO base reload + +Or `sekft-resident --base --selftest-data ` to prove the base +loads once and two adapters train against it. +""" +from __future__ import annotations + +import argparse +import gc +import json +from pathlib import Path +from typing import Any + +import torch +from datasets import Dataset +from peft import (LoraConfig, PeftModel, get_peft_model, + prepare_model_for_kbit_training) +from transformers import (AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, + DataCollatorForSeq2Seq, Trainer, TrainingArguments) + +from .sft import build_masked_example, iter_keepers, normalize_for_template + +LORA_TARGETS = ["q_proj", "k_proj", "v_proj", "o_proj"] + + +def _free() -> None: + gc.collect() + torch.cuda.empty_cache() + + +class Resident: + """A base model held resident on the GPU; adapters cycle through it.""" + + def __init__(self, base: str, load_4bit: bool = False) -> None: + self.base_path = str(Path(base).expanduser()) + self.load_4bit = load_4bit + self.tok = AutoTokenizer.from_pretrained(self.base_path) + if self.tok.pad_token is None: + self.tok.pad_token = self.tok.eos_token + quant = None + if load_4bit: + quant = BitsAndBytesConfig( + load_in_4bit=True, bnb_4bit_quant_type="nf4", + bnb_4bit_compute_dtype=torch.float16, bnb_4bit_use_double_quant=True) + print(f"[resident] loading base ONCE: {self.base_path} (4bit={load_4bit}) ...") + self.base = AutoModelForCausalLM.from_pretrained( + self.base_path, dtype=torch.float16, quantization_config=quant) + self.base = (prepare_model_for_kbit_training(self.base) if load_4bit + else self.base) + if not load_4bit: + self.base.enable_input_require_grads() + dev = next(self.base.parameters()).device + mem = torch.cuda.memory_allocated() / 1e9 + print(f"[resident] base resident on {dev}; {mem:.1f} GB VRAM") + + # -- build masked rows from kept trajectories -------------------------- + + def _rows(self, data_dir: Path, max_len: int) -> list[dict[str, list[Any]]]: + rows = [] + for turns in iter_keepers(data_dir): + ex = build_masked_example(turns, self.tok) + if len(ex["input_ids"]) <= max_len and any(l != -100 for l in ex["labels"]): + rows.append(ex) + if not rows: + raise SystemExit(f"no usable keeper trajectories in {data_dir}") + return rows + + # -- train a fresh adapter on the resident base ------------------------ + + def fit(self, data_dir: str, out: str, lora_r: int = 16, lr: float = 2e-4, + epochs: float = 3.0, batch: int = 1, accum: int = 8, + max_len: int = 4096) -> Path: + ddir, odir = Path(data_dir).expanduser(), Path(out).expanduser() + ds = Dataset.from_list(self._rows(ddir, max_len)) + if not self.load_4bit: + self.base.gradient_checkpointing_enable() + model = get_peft_model(self.base, LoraConfig( + r=lora_r, lora_alpha=lora_r * 2, lora_dropout=0.05, + task_type="CAUSAL_LM", target_modules=LORA_TARGETS)) + model.print_trainable_parameters() + args = TrainingArguments( + output_dir=str(odir), per_device_train_batch_size=batch, + gradient_accumulation_steps=accum, num_train_epochs=epochs, + learning_rate=lr, fp16=True, logging_steps=1, save_strategy="no", + report_to=["tensorboard"], logging_dir=str(odir / "runs"), + remove_unused_columns=False, warmup_ratio=0.03) + tr = Trainer(model=model, args=args, train_dataset=ds, + data_collator=DataCollatorForSeq2Seq( + self.tok, padding=True, label_pad_token_id=-100)) + tr.train() + odir.mkdir(parents=True, exist_ok=True) + model.save_pretrained(str(odir)) + self.tok.save_pretrained(str(odir)) + (odir / "log_history.jsonl").write_text( + "\n".join(json.dumps(r) for r in tr.state.log_history)) + losses = [h["loss"] for h in tr.state.log_history if "loss" in h] + print(f"[resident] fit -> {odir} final loss {losses[-1] if losses else '?'}") + self.base = model.unload() # strip LoRA, restore resident base + del model, tr, ds + _free() + return odir + + # -- behavioural eval of a saved adapter ------------------------------- + + def evaluate(self, adapter: str, scenarios_dir: str, n: int = 10, + max_steps: int = 30, temperature: float = 0.7) -> dict[str, Any]: + from tiararodney.posix_sdc.factory.dashdocker import DashDocker, available + from tiararodney.posix_sdc.factory.rollout import rollout + from tiararodney.posix_sdc.schema import Scenario + if not available(): + raise SystemExit("sekft-dash image unavailable on this box") + # adapter=None -> evaluate the BASE model (the within-holdout baseline). + if adapter: + adapter = str(Path(adapter).expanduser()) + pm = PeftModel.from_pretrained(self.base, adapter) + else: + pm = self.base + pm.eval() + + def operator(messages: list[dict[str, str]]) -> str: + msgs = normalize_for_template(messages) + ids = self.tok.apply_chat_template( + msgs, add_generation_prompt=True, return_tensors="pt").to(pm.device) + with torch.no_grad(): + o = pm.generate(ids, max_new_tokens=64, do_sample=temperature > 0, + temperature=max(temperature, 1e-2), + eos_token_id=self.tok.eos_token_id, + pad_token_id=self.tok.eos_token_id) + text: str = self.tok.decode(o[0][ids.shape[1]:], skip_special_tokens=True).strip() + return text + + backend = DashDocker() + rows = [] + for f in sorted(Path(scenarios_dir).expanduser().glob("*.json"))[:n]: + sc = Scenario.from_dict(json.loads(f.read_text())) + tj = rollout(sc, backend, max_steps=max_steps, temperature=temperature, + operator=operator, use_scaffold=False) + rows.append(tj) + print(f" {sc.id}: {tj.outcome} terminal={tj.terminal} verified={tj.verified}") + d = len(rows) or 1 + m = { + "n": len(rows), + "operate_rate": round(sum(bool(t.steps > 0 and t.meta.get("clean")) for t in rows) / d, 3), + "terminate_rate": round(sum(t.terminal in ("exit", "panic") for t in rows) / d, 3), + "verified_rate": round(sum(t.verified for t in rows) / d, 3), + "clean_rate": round(sum(t.keep for t in rows) / d, 3), + } + if adapter: # base is unwrapped only if we wrapped it + self.base = pm.unload() + del pm + _free() + print("[resident] eval:", json.dumps(m)) + return m + + +def main() -> None: + ap = argparse.ArgumentParser(description="Resident base; cycle adapters.") + ap.add_argument("--base", required=True) + ap.add_argument("--load-4bit", action="store_true") + ap.add_argument("--selftest-data", + help="fit two adapters on this data to prove resident multi-fit") + ns = ap.parse_args() + r = Resident(ns.base, ns.load_4bit) + if ns.selftest_data: + print("=== selftest: two fits on the SAME resident base (no reload) ===") + r.fit(ns.selftest_data, "/tmp/res-a", epochs=1, lora_r=8) + r.fit(ns.selftest_data, "/tmp/res-b", epochs=1, lora_r=8) + print("=== selftest OK: base loaded once, two adapters trained ===") + else: + print("Resident ready. Import and use r.fit() / r.evaluate(), " + "or pass --selftest-data .") + + +if __name__ == "__main__": + main() diff --git a/src/tiararodney/sekft/sft.py b/src/tiararodney/sekft/sft.py new file mode 100644 index 0000000..ce0e478 --- /dev/null +++ b/src/tiararodney/sekft/sft.py @@ -0,0 +1,289 @@ +"""sekft trainer: SFT a base model on kept shell-operation trajectories. + +Trains assistant turns ONLY -- the commands and the terminal ``exit`` / ``panic``. +The environment turns (system orientation, prompts, command output) are masked +to ``-100`` so the model learns to *produce* commands, not to predict the +environment's replies. Getting this mask wrong is the classic way to ruin a +shell-operator SFT (the model starts hallucinating output), so it is the part +worth testing hardest -- and it is framework-independent. + +Render uses the tokenizer's OWN chat template (``apply_chat_template``), so the +training render is identical to what the serving harness produces (ccpty sends +structured messages and the inference endpoint applies the model's default +template). Trajectories are canonicalised first (``normalize_for_template``): +a leading ``system`` turn is folded into the first ``user`` turn and consecutive +same-role turns are merged, because instruct templates such as Mistral's have no +system role and require strict user/assistant alternation. That same +canonicalisation must run on the serving side. Everything else is standard +causal-LM SFT with an assistant-only loss mask. + + sekft-train --data ./trajectories --base --out ./ckpt + sekft-train --data corpus.jsonl --base # a curated .jsonl corpus + sekft-train --hub --base # the published corpus (Hub) + sekft-train --data ./trajectories --base --inspect # mask stats, no training + +Training needs torch + transformers + peft (a GPU box). ``--inspect`` and the +normalize/mask helpers run anywhere a tokenizer with a chat template is +available. +""" +from __future__ import annotations + +import argparse +import json +import logging +from collections.abc import Iterator +from pathlib import Path +from typing import Any + +from ._log import setup as _setup_logging + +log = logging.getLogger("sekft.train") + + +def normalize_for_template(messages: list[dict[str, str]]) -> list[dict[str, str]]: + """Canonicalise a trajectory for instruct chat templates that have no system + role and require strict user/assistant alternation (Mistral and friends): + treat ``system`` as ``user``, then merge consecutive same-role turns by + joining their content with a newline. + + This is loss-neutral for the assistant mask (only environment/user turns + ever merge; the assistant commands are never adjacent in this data) and it + is what lets ``apply_chat_template`` render the multi-turn shell dialogue. + The serving side MUST apply the same canonicalisation, or train and serve + diverge again. + """ + out: list[dict[str, str]] = [] + for m in messages: + role = "user" if m["role"] == "system" else m["role"] + if out and out[-1]["role"] == role: + out[-1] = {"role": role, "content": out[-1]["content"] + "\n" + m["content"]} + else: + out.append({"role": role, "content": m["content"]}) + return out + + +def build_masked_example(messages: list[dict[str, str]], tokenizer: Any) -> dict[str, list[Any]]: + """Tokenize a trajectory with the tokenizer's OWN chat template and build an + assistant-only loss mask. + + The render is ``tokenizer.apply_chat_template`` on the canonicalised turns, + so it is byte-identical to what the serving harness sends. The mask is + derived by token-prefix differencing: the tokens an assistant turn + contributes are exactly those that appear when it extends the rendered + prefix, which trains the commands plus the template's end-of-turn token (so + the model learns to stop) and masks every environment turn to ``-100``. This + assumes an additive template (each turn extends the previous render); a + non-additive one raises rather than silently mis-mask. + """ + msgs = normalize_for_template(messages) + ids = tokenizer.apply_chat_template(msgs, add_generation_prompt=False) + labels = [-100] * len(ids) + prev: list[int] = [] + for i, m in enumerate(msgs): + upto = tokenizer.apply_chat_template(msgs[:i + 1], add_generation_prompt=False) + if ids[:len(upto)] != upto or upto[:len(prev)] != prev: + raise ValueError("chat template is not additive; cannot derive an " + "assistant loss mask by token-prefix differencing") + if m["role"] == "assistant": + for j in range(len(prev), len(upto)): + labels[j] = ids[j] + prev = upto + return {"input_ids": ids, "attention_mask": [1] * len(ids), "labels": labels} + + +def iter_keepers(data_dir: Path) -> Iterator[list[dict[str, str]]]: + """Yield ``turns`` (message lists) from raw rollout JSONs marked keep.""" + for f in sorted(data_dir.glob("*.json")): + d = json.loads(f.read_text()) + if d.get("keep"): + yield d["turns"] + + +def load_turns(data: Path, hub: bool = False, + revision: str | None = None) -> Iterator[list[dict[str, str]]]: + """Yield assistant-bearing ``turns`` from one of three sources: + + - ``--hub``: the published corpus via posix-sdc's ``load_trajectories`` (the + in-repo ``data/`` of a posix-sdc checkout, else the Hugging Face Hub); + - ``data`` a ``.jsonl`` file: a curated corpus, already keep-filtered, one + record per line; + - ``data`` a directory: raw rollout ``.json`` (keep-filtered here). + + posix-sdc is imported lazily, so the raw-dir and ``.jsonl`` paths need + neither posix-sdc nor huggingface_hub installed. + """ + if hub: + from tiararodney.posix_sdc import load_trajectories + for r in load_trajectories(revision=revision): + yield r["turns"] + elif data.is_dir(): + yield from iter_keepers(data) + elif data.suffix == ".jsonl": + with open(data) as fh: + for line in fh: + if line.strip(): + yield json.loads(line)["turns"] + else: + raise SystemExit( + f"--data must be a rollout directory or a .jsonl corpus (got {data})") + + +def mask_stats(example: dict[str, list[Any]]) -> tuple[int, int]: + """(trained tokens, total tokens) for an example.""" + trained = sum(1 for x in example["labels"] if x != -100) + return trained, len(example["labels"]) + + +# -------------------------------------------------------------------------- +# Training (GPU box: torch + transformers + peft) +# -------------------------------------------------------------------------- + +def train(data_dir: Path, base: str, out: Path, epochs: float, lr: float, + batch: int, accum: int, max_len: int, lora_r: int, + load_4bit: bool = False, hub: bool = False, + revision: str | None = None) -> None: + import torch + from datasets import Dataset + from peft import LoraConfig, get_peft_model + from transformers import (AutoModelForCausalLM, AutoTokenizer, + DataCollatorForSeq2Seq, Trainer, TrainingArguments) + from transformers.utils import logging as hf_logging + + # Surface the Trainer's own per-step curve (loss/lr/grad_norm); it is at + # WARNING by default, which is most of why training looks silent. + hf_logging.set_verbosity_info() + + source = "hub" if hub else data_dir + log.info("base=%s data=%s out=%s", base, source, out) + log.info("loading tokenizer: %s", base) + tok = AutoTokenizer.from_pretrained(base) + if tok.pad_token is None: + tok.pad_token = tok.eos_token + + log.info("building masked examples from %s ...", source) + rows: list[dict[str, list[Any]]] = [] + n_seen = n_long = n_empty = 0 + for turns in load_turns(data_dir, hub=hub, revision=revision): + n_seen += 1 + ex = build_masked_example(turns, tok) + log.debug(" trajectory %d: %d turns -> %d tokens, %d trained", + n_seen, len(turns), len(ex["input_ids"]), mask_stats(ex)[0]) + if n_seen % 100 == 0: + log.info(" ... %d trajectories processed, %d usable", n_seen, len(rows)) + if len(ex["input_ids"]) > max_len: + n_long += 1 + continue + if not any(l != -100 for l in ex["labels"]): + n_empty += 1 + continue + rows.append(ex) + if not rows: + raise SystemExit(f"no usable keeper trajectories in {data_dir}") + trained = sum(mask_stats(r)[0] for r in rows) + total = sum(mask_stats(r)[1] for r in rows) + log.info("dataset: %d keepers -> %d usable; %d trained / %d tokens (%.1f%% assistant)", + n_seen, len(rows), trained, total, 100 * trained / total) + if n_long or n_empty: + log.warning("dropped %d trajectories: %d over --max-len %d, %d empty-mask", + n_long + n_empty, n_long, max_len, n_empty) + ds = Dataset.from_list(rows) + + # 4-bit (QLoRA) shrinks the base from ~14 GB to ~4 GB to move across the + # OcuLink/PCIe link and to hold in VRAM; nf4 + fp16 compute works on the + # V100 (sm_70). Without it, plain fp16 weights. + quant = None + if load_4bit: + from transformers import BitsAndBytesConfig + quant = BitsAndBytesConfig( + load_in_4bit=True, bnb_4bit_quant_type="nf4", + bnb_4bit_compute_dtype=torch.float16, bnb_4bit_use_double_quant=True, + ) + log.info("loading base model: %s (%s)", base, + "4-bit QLoRA" if load_4bit else "fp16") + model = AutoModelForCausalLM.from_pretrained( + base, dtype=torch.float16, quantization_config=quant) + if load_4bit: + from peft import prepare_model_for_kbit_training + model = prepare_model_for_kbit_training(model) # handles ckpt + input grads + else: + model.enable_input_require_grads() + model.gradient_checkpointing_enable() + model = get_peft_model(model, LoraConfig( + r=lora_r, lora_alpha=lora_r * 2, lora_dropout=0.05, task_type="CAUSAL_LM", + target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], + )) + n_train, n_all = model.get_nb_trainable_parameters() + log.info("LoRA r=%d: %d trainable / %d params (%.3f%%)", + lora_r, n_train, n_all, 100 * n_train / n_all) + + args = TrainingArguments( + output_dir=str(out), per_device_train_batch_size=batch, + gradient_accumulation_steps=accum, num_train_epochs=epochs, + learning_rate=lr, fp16=True, logging_steps=1, save_strategy="epoch", + report_to=["tensorboard"], logging_dir=str(out / "runs"), + remove_unused_columns=False, warmup_ratio=0.03, + ) + trainer = Trainer( + model=model, args=args, train_dataset=ds, + data_collator=DataCollatorForSeq2Seq(tok, padding=True, label_pad_token_id=-100), + ) + log.info("training: %g epochs, lr=%g, batch=%d x accum=%d (effective %d), max_len=%d", + epochs, lr, batch, accum, batch * accum, max_len) + trainer.train() + model.save_pretrained(str(out)) + tok.save_pretrained(str(out)) + # durable, greppable record of the curve (loss/lr/grad_norm per step). + (out / "log_history.jsonl").write_text( + "\n".join(json.dumps(r) for r in trainer.state.log_history)) + log.info("saved LoRA adapter + log_history.jsonl -> %s (tensorboard: --logdir %s)", + out, out / "runs") + + +def inspect(data_dir: Path, base: str, hub: bool = False, + revision: str | None = None) -> None: + from transformers import AutoTokenizer + log.info("loading tokenizer: %s", base) + tok = AutoTokenizer.from_pretrained(base) + n = tt = tr = 0 + for turns in load_turns(data_dir, hub=hub, revision=revision): + ex = build_masked_example(turns, tok) + t, total = mask_stats(ex) + tr += t; tt += total; n += 1 + if not n: + raise SystemExit(f"no keeper trajectories in {data_dir}") + log.info("%d keeper trajectories; %d/%d tokens trained (%.1f%% assistant, rest masked)", + n, tr, tt, 100 * tr / tt) + + +def main() -> None: + ap = argparse.ArgumentParser(description="SFT a model on shell trajectories.") + ap.add_argument("--data", type=Path, default=Path("./trajectories"), + help="a raw rollout dir or a curated .jsonl corpus") + ap.add_argument("--hub", action="store_true", + help="load the published corpus via posix-sdc (Hub); ignores --data") + ap.add_argument("--revision", default=None, + help="dataset revision/tag to pin when using --hub") + ap.add_argument("--base", required=True, help="HF model id or local dir") + ap.add_argument("--out", type=Path, default=Path("./ckpt")) + ap.add_argument("--inspect", action="store_true", help="mask stats only, no training") + ap.add_argument("--epochs", type=float, default=3.0) + ap.add_argument("--lr", type=float, default=2e-4) + ap.add_argument("--batch", type=int, default=1) + ap.add_argument("--accum", type=int, default=8) + ap.add_argument("--max-len", type=int, default=4096) + ap.add_argument("--lora-r", type=int, default=16) + ap.add_argument("--load-4bit", action="store_true", + help="QLoRA: load base in 4-bit (less to move over the link, less VRAM)") + ap.add_argument("-v", "--verbose", action="store_true", help="debug-level logging") + ap.add_argument("-q", "--quiet", action="store_true", help="warnings and errors only") + ns = ap.parse_args() + _setup_logging(verbose=ns.verbose, quiet=ns.quiet) + if ns.inspect: + inspect(ns.data, ns.base, hub=ns.hub, revision=ns.revision) + else: + train(ns.data, ns.base, ns.out, ns.epochs, ns.lr, ns.batch, ns.accum, + ns.max_len, ns.lora_r, ns.load_4bit, hub=ns.hub, revision=ns.revision) + + +if __name__ == "__main__": + main() diff --git a/tests/smoke/test_entrypoints.py b/tests/smoke/test_entrypoints.py new file mode 100644 index 0000000..6d6c474 --- /dev/null +++ b/tests/smoke/test_entrypoints.py @@ -0,0 +1,30 @@ +"""Smoke tests: the console entry points load and respond to --help without the +GPU stack (torch is imported lazily inside the training/eval code paths).""" +from __future__ import annotations + +import os +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +SRC = ROOT / "src" +POSIX_SRC = ROOT.parent / "posix-sdc" / "src" + + +def _help(module: str) -> "subprocess.CompletedProcess[str]": + env = dict(os.environ, PYTHONPATH=os.pathsep.join([str(SRC), str(POSIX_SRC)])) + return subprocess.run([sys.executable, "-m", module, "--help"], + capture_output=True, text=True, env=env) + + +def test_train_help() -> None: + cp = _help("tiararodney.sekft.sft") + assert cp.returncode == 0, cp.stderr + assert "--data" in cp.stdout + + +def test_eval_help() -> None: + cp = _help("tiararodney.sekft.eval") + assert cp.returncode == 0, cp.stderr + assert "--adapter" in cp.stdout diff --git a/tests/unit/test_load.py b/tests/unit/test_load.py new file mode 100644 index 0000000..4106da6 --- /dev/null +++ b/tests/unit/test_load.py @@ -0,0 +1,35 @@ +"""Unit tests for the trainer's three-source data loader (raw dir / curated +jsonl). The Hub path delegates to posix-sdc and is covered there.""" +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from tiararodney.sekft import sft + + +def test_load_turns_from_raw_dir(tmp_path: Path) -> None: + (tmp_path / "a.json").write_text(json.dumps( + {"keep": True, "turns": [{"role": "assistant", "content": "ls"}]})) + (tmp_path / "b.json").write_text(json.dumps( # not kept -> excluded + {"keep": False, "turns": [{"role": "assistant", "content": "rm -rf /"}]})) + got = list(sft.load_turns(tmp_path)) + assert len(got) == 1 + assert got[0][0]["content"] == "ls" + + +def test_load_turns_from_jsonl(tmp_path: Path) -> None: + f = tmp_path / "corpus.jsonl" + f.write_text("\n".join(json.dumps({"turns": [{"role": "assistant", "content": c}]}) + for c in ("ls", "cat x")) + "\n") + got = list(sft.load_turns(f)) + assert [t[0]["content"] for t in got] == ["ls", "cat x"] + + +def test_load_turns_rejects_other_paths(tmp_path: Path) -> None: + bad = tmp_path / "notes.txt" + bad.write_text("hi") + with pytest.raises(SystemExit): + list(sft.load_turns(bad)) diff --git a/tests/unit/test_sft.py b/tests/unit/test_sft.py new file mode 100644 index 0000000..d24eef0 --- /dev/null +++ b/tests/unit/test_sft.py @@ -0,0 +1,75 @@ +"""Unit tests for the SFT render canonicalisation and assistant-only mask. + +These run anywhere: a fake additive tokenizer stands in for a real chat +template, so no torch/transformers is needed.""" +from __future__ import annotations + +from typing import Any + +import pytest + +from tiararodney.sekft import sft + + +class FakeTok: + """Additive chat template: each turn renders to `` tokens... ``; + the generation prompt appends ````.""" + + def apply_chat_template(self, msgs: list[dict[str, str]], add_generation_prompt: bool = False, + return_tensors: Any = None) -> list[str]: + toks: list[str] = [] + for m in msgs: + toks.append(f"<{m['role']}>") + toks += m["content"].split() + toks.append("") + if add_generation_prompt: + toks.append("") + return toks + + +def test_normalize_folds_system_and_merges_consecutive() -> None: + raw = [ + {"role": "system", "content": "orient"}, + {"role": "user", "content": "login"}, + {"role": "user", "content": "prompt"}, + {"role": "assistant", "content": "cat f"}, + {"role": "user", "content": "out"}, + {"role": "user", "content": "prompt"}, + {"role": "assistant", "content": "exit"}, + ] + norm = sft.normalize_for_template(raw) + assert [m["role"] for m in norm] == ["user", "assistant", "user", "assistant"] + assert norm[0]["content"] == "orient\nlogin\nprompt" + + +def test_normalize_leaves_clean_alternation_untouched() -> None: + raw = [{"role": "user", "content": "a"}, {"role": "assistant", "content": "b"}] + assert sft.normalize_for_template(raw) == raw + + +def test_mask_trains_assistant_turns_only() -> None: + raw = [ + {"role": "system", "content": "orient"}, + {"role": "user", "content": "login"}, + {"role": "assistant", "content": "cat f"}, + {"role": "user", "content": "out"}, + {"role": "assistant", "content": "exit"}, + ] + ex = sft.build_masked_example(raw, FakeTok()) + trained = [t for t, lab in zip(ex["input_ids"], ex["labels"]) if lab != -100] + masked = [t for t, lab in zip(ex["input_ids"], ex["labels"]) if lab == -100] + assert set(trained) <= {"", "cat", "f", "exit", ""} + assert "cat" in trained and "exit" in trained # both commands present + assert {"orient", "login", "out"} <= set(masked) # environment masked + + +def test_mask_raises_on_non_additive_template() -> None: + class BadTok: + def apply_chat_template(self, msgs: list[dict[str, str]], add_generation_prompt: bool = False, + return_tensors: Any = None) -> list[int]: + return list(range(len(msgs), 0, -1)) # reversed: prefixes do not nest + + with pytest.raises(ValueError): + sft.build_masked_example( + [{"role": "user", "content": "a"}, {"role": "assistant", "content": "b"}], + BadTok()) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..917e98a --- /dev/null +++ b/tox.ini @@ -0,0 +1,47 @@ +[tox] +requires = + tox>=4.19 +env_list = + unit-py3{9-13} + smoke-py3{9-13} + lint + format + +[testenv] +deps = + ../posix-sdc + . + +[testenv:lint] +description = run type check on code base +labels = static +deps = + mypy +commands = + mypy src tests --junit-xml test-reports/{env_name}.xml + +[testenv:format] +description = check formatting +labels = static +deps = + autopep8 +commands = + autopep8 --diff --exit-code src tests + +[testenv:unit-py3{9-13}] +description = run unit tests +labels = unit +deps = + {[testenv]deps} + pytest +commands = + pytest tests/unit --junitxml=test-reports/{env_name}.xml + +[testenv:smoke-py3{9-13}] +description = run smoke tests against the console entry points +labels = smoke +deps = + {[testenv]deps} + pytest +commands = + pytest tests/smoke --junitxml=test-reports/{env_name}.xml