├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── MANIFEST.in ├── Pipfile ├── Pipfile.lock ├── README.md ├── setup.py └── src └── botsh ├── __init__.py ├── docker_exec.py ├── history.py ├── llm.py ├── logging.py ├── main.py ├── prompt.py ├── task_driver.py └── templates └── prompt.jinja2 /.gitignore: -------------------------------------------------------------------------------- 1 | output 2 | __pycache__ 3 | transcripts 4 | *.egg-info 5 | dist 6 | build 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.provider": "black" 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Drifting in Space 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include src/botsh/templates/*.jinja2 2 | include Pipfile* 3 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | docker = "*" 8 | openai = "*" 9 | termcolor = "*" 10 | jinja2 = "*" 11 | structlog = "*" 12 | 13 | [dev-packages] 14 | black = "*" 15 | twine = "*" 16 | 17 | [requires] 18 | python_version = "3.10" 19 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "3a9a2cac52c6b3d9d58438304f433eb28fc861876854f4ba68f2820aa348e8e7" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.10" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "aiohttp": { 20 | "hashes": [ 21 | "sha256:03543dcf98a6619254b409be2d22b51f21ec66272be4ebda7b04e6412e4b2e14", 22 | "sha256:03baa76b730e4e15a45f81dfe29a8d910314143414e528737f8589ec60cf7391", 23 | "sha256:0a63f03189a6fa7c900226e3ef5ba4d3bd047e18f445e69adbd65af433add5a2", 24 | "sha256:10c8cefcff98fd9168cdd86c4da8b84baaa90bf2da2269c6161984e6737bf23e", 25 | "sha256:147ae376f14b55f4f3c2b118b95be50a369b89b38a971e80a17c3fd623f280c9", 26 | "sha256:176a64b24c0935869d5bbc4c96e82f89f643bcdf08ec947701b9dbb3c956b7dd", 27 | "sha256:17b79c2963db82086229012cff93ea55196ed31f6493bb1ccd2c62f1724324e4", 28 | "sha256:1a45865451439eb320784918617ba54b7a377e3501fb70402ab84d38c2cd891b", 29 | "sha256:1b3ea7edd2d24538959c1c1abf97c744d879d4e541d38305f9bd7d9b10c9ec41", 30 | "sha256:22f6eab15b6db242499a16de87939a342f5a950ad0abaf1532038e2ce7d31567", 31 | "sha256:3032dcb1c35bc330134a5b8a5d4f68c1a87252dfc6e1262c65a7e30e62298275", 32 | "sha256:33587f26dcee66efb2fff3c177547bd0449ab7edf1b73a7f5dea1e38609a0c54", 33 | "sha256:34ce9f93a4a68d1272d26030655dd1b58ff727b3ed2a33d80ec433561b03d67a", 34 | "sha256:3a80464982d41b1fbfe3154e440ba4904b71c1a53e9cd584098cd41efdb188ef", 35 | "sha256:3b90467ebc3d9fa5b0f9b6489dfb2c304a1db7b9946fa92aa76a831b9d587e99", 36 | "sha256:3d89efa095ca7d442a6d0cbc755f9e08190ba40069b235c9886a8763b03785da", 37 | "sha256:3d8ef1a630519a26d6760bc695842579cb09e373c5f227a21b67dc3eb16cfea4", 38 | "sha256:3f43255086fe25e36fd5ed8f2ee47477408a73ef00e804cb2b5cba4bf2ac7f5e", 39 | "sha256:40653609b3bf50611356e6b6554e3a331f6879fa7116f3959b20e3528783e699", 40 | "sha256:41a86a69bb63bb2fc3dc9ad5ea9f10f1c9c8e282b471931be0268ddd09430b04", 41 | "sha256:493f5bc2f8307286b7799c6d899d388bbaa7dfa6c4caf4f97ef7521b9cb13719", 42 | "sha256:4a6cadebe132e90cefa77e45f2d2f1a4b2ce5c6b1bfc1656c1ddafcfe4ba8131", 43 | "sha256:4c745b109057e7e5f1848c689ee4fb3a016c8d4d92da52b312f8a509f83aa05e", 44 | "sha256:4d347a172f866cd1d93126d9b239fcbe682acb39b48ee0873c73c933dd23bd0f", 45 | "sha256:4dac314662f4e2aa5009977b652d9b8db7121b46c38f2073bfeed9f4049732cd", 46 | "sha256:4ddaae3f3d32fc2cb4c53fab020b69a05c8ab1f02e0e59665c6f7a0d3a5be54f", 47 | "sha256:5393fb786a9e23e4799fec788e7e735de18052f83682ce2dfcabaf1c00c2c08e", 48 | "sha256:59f029a5f6e2d679296db7bee982bb3d20c088e52a2977e3175faf31d6fb75d1", 49 | "sha256:5a7bdf9e57126dc345b683c3632e8ba317c31d2a41acd5800c10640387d193ed", 50 | "sha256:5b3f2e06a512e94722886c0827bee9807c86a9f698fac6b3aee841fab49bbfb4", 51 | "sha256:5ce45967538fb747370308d3145aa68a074bdecb4f3a300869590f725ced69c1", 52 | "sha256:5e14f25765a578a0a634d5f0cd1e2c3f53964553a00347998dfdf96b8137f777", 53 | "sha256:618c901dd3aad4ace71dfa0f5e82e88b46ef57e3239fc7027773cb6d4ed53531", 54 | "sha256:652b1bff4f15f6287550b4670546a2947f2a4575b6c6dff7760eafb22eacbf0b", 55 | "sha256:6c08e8ed6fa3d477e501ec9db169bfac8140e830aa372d77e4a43084d8dd91ab", 56 | "sha256:6ddb2a2026c3f6a68c3998a6c47ab6795e4127315d2e35a09997da21865757f8", 57 | "sha256:6e601588f2b502c93c30cd5a45bfc665faaf37bbe835b7cfd461753068232074", 58 | "sha256:6e74dd54f7239fcffe07913ff8b964e28b712f09846e20de78676ce2a3dc0bfc", 59 | "sha256:7235604476a76ef249bd64cb8274ed24ccf6995c4a8b51a237005ee7a57e8643", 60 | "sha256:7ab43061a0c81198d88f39aaf90dae9a7744620978f7ef3e3708339b8ed2ef01", 61 | "sha256:7c7837fe8037e96b6dd5cfcf47263c1620a9d332a87ec06a6ca4564e56bd0f36", 62 | "sha256:80575ba9377c5171407a06d0196b2310b679dc752d02a1fcaa2bc20b235dbf24", 63 | "sha256:80a37fe8f7c1e6ce8f2d9c411676e4bc633a8462844e38f46156d07a7d401654", 64 | "sha256:8189c56eb0ddbb95bfadb8f60ea1b22fcfa659396ea36f6adcc521213cd7b44d", 65 | "sha256:854f422ac44af92bfe172d8e73229c270dc09b96535e8a548f99c84f82dde241", 66 | "sha256:880e15bb6dad90549b43f796b391cfffd7af373f4646784795e20d92606b7a51", 67 | "sha256:8b631e26df63e52f7cce0cce6507b7a7f1bc9b0c501fcde69742130b32e8782f", 68 | "sha256:8c29c77cc57e40f84acef9bfb904373a4e89a4e8b74e71aa8075c021ec9078c2", 69 | "sha256:91f6d540163f90bbaef9387e65f18f73ffd7c79f5225ac3d3f61df7b0d01ad15", 70 | "sha256:92c0cea74a2a81c4c76b62ea1cac163ecb20fb3ba3a75c909b9fa71b4ad493cf", 71 | "sha256:9bcb89336efa095ea21b30f9e686763f2be4478f1b0a616969551982c4ee4c3b", 72 | "sha256:a1f4689c9a1462f3df0a1f7e797791cd6b124ddbee2b570d34e7f38ade0e2c71", 73 | "sha256:a3fec6a4cb5551721cdd70473eb009d90935b4063acc5f40905d40ecfea23e05", 74 | "sha256:a5d794d1ae64e7753e405ba58e08fcfa73e3fad93ef9b7e31112ef3c9a0efb52", 75 | "sha256:a86d42d7cba1cec432d47ab13b6637bee393a10f664c425ea7b305d1301ca1a3", 76 | "sha256:adfbc22e87365a6e564c804c58fc44ff7727deea782d175c33602737b7feadb6", 77 | "sha256:aeb29c84bb53a84b1a81c6c09d24cf33bb8432cc5c39979021cc0f98c1292a1a", 78 | "sha256:aede4df4eeb926c8fa70de46c340a1bc2c6079e1c40ccf7b0eae1313ffd33519", 79 | "sha256:b744c33b6f14ca26b7544e8d8aadff6b765a80ad6164fb1a430bbadd593dfb1a", 80 | "sha256:b7a00a9ed8d6e725b55ef98b1b35c88013245f35f68b1b12c5cd4100dddac333", 81 | "sha256:bb96fa6b56bb536c42d6a4a87dfca570ff8e52de2d63cabebfd6fb67049c34b6", 82 | "sha256:bbcf1a76cf6f6dacf2c7f4d2ebd411438c275faa1dc0c68e46eb84eebd05dd7d", 83 | "sha256:bca5f24726e2919de94f047739d0a4fc01372801a3672708260546aa2601bf57", 84 | "sha256:bf2e1a9162c1e441bf805a1fd166e249d574ca04e03b34f97e2928769e91ab5c", 85 | "sha256:c4eb3b82ca349cf6fadcdc7abcc8b3a50ab74a62e9113ab7a8ebc268aad35bb9", 86 | "sha256:c6cc15d58053c76eacac5fa9152d7d84b8d67b3fde92709195cb984cfb3475ea", 87 | "sha256:c6cd05ea06daca6ad6a4ca3ba7fe7dc5b5de063ff4daec6170ec0f9979f6c332", 88 | "sha256:c844fd628851c0bc309f3c801b3a3d58ce430b2ce5b359cd918a5a76d0b20cb5", 89 | "sha256:c9cb1565a7ad52e096a6988e2ee0397f72fe056dadf75d17fa6b5aebaea05622", 90 | "sha256:cab9401de3ea52b4b4c6971db5fb5c999bd4260898af972bf23de1c6b5dd9d71", 91 | "sha256:cd468460eefef601ece4428d3cf4562459157c0f6523db89365202c31b6daebb", 92 | "sha256:d1e6a862b76f34395a985b3cd39a0d949ca80a70b6ebdea37d3ab39ceea6698a", 93 | "sha256:d1f9282c5f2b5e241034a009779e7b2a1aa045f667ff521e7948ea9b56e0c5ff", 94 | "sha256:d265f09a75a79a788237d7f9054f929ced2e69eb0bb79de3798c468d8a90f945", 95 | "sha256:db3fc6120bce9f446d13b1b834ea5b15341ca9ff3f335e4a951a6ead31105480", 96 | "sha256:dbf3a08a06b3f433013c143ebd72c15cac33d2914b8ea4bea7ac2c23578815d6", 97 | "sha256:de04b491d0e5007ee1b63a309956eaed959a49f5bb4e84b26c8f5d49de140fa9", 98 | "sha256:e4b09863aae0dc965c3ef36500d891a3ff495a2ea9ae9171e4519963c12ceefd", 99 | "sha256:e595432ac259af2d4630008bf638873d69346372d38255774c0e286951e8b79f", 100 | "sha256:e75b89ac3bd27d2d043b234aa7b734c38ba1b0e43f07787130a0ecac1e12228a", 101 | "sha256:ea9eb976ffdd79d0e893869cfe179a8f60f152d42cb64622fca418cd9b18dc2a", 102 | "sha256:eafb3e874816ebe2a92f5e155f17260034c8c341dad1df25672fb710627c6949", 103 | "sha256:ee3c36df21b5714d49fc4580247947aa64bcbe2939d1b77b4c8dcb8f6c9faecc", 104 | "sha256:f352b62b45dff37b55ddd7b9c0c8672c4dd2eb9c0f9c11d395075a84e2c40f75", 105 | "sha256:fabb87dd8850ef0f7fe2b366d44b77d7e6fa2ea87861ab3844da99291e81e60f", 106 | "sha256:fe11310ae1e4cd560035598c3f29d86cef39a83d244c7466f95c27ae04850f10", 107 | "sha256:fe7ba4a51f33ab275515f66b0a236bcde4fb5561498fe8f898d4e549b2e4509f" 108 | ], 109 | "markers": "python_version >= '3.6'", 110 | "version": "==3.8.4" 111 | }, 112 | "aiosignal": { 113 | "hashes": [ 114 | "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", 115 | "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17" 116 | ], 117 | "markers": "python_version >= '3.7'", 118 | "version": "==1.3.1" 119 | }, 120 | "async-timeout": { 121 | "hashes": [ 122 | "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15", 123 | "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c" 124 | ], 125 | "markers": "python_version >= '3.6'", 126 | "version": "==4.0.2" 127 | }, 128 | "attrs": { 129 | "hashes": [ 130 | "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836", 131 | "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99" 132 | ], 133 | "markers": "python_version >= '3.6'", 134 | "version": "==22.2.0" 135 | }, 136 | "certifi": { 137 | "hashes": [ 138 | "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", 139 | "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" 140 | ], 141 | "markers": "python_version >= '3.6'", 142 | "version": "==2022.12.7" 143 | }, 144 | "charset-normalizer": { 145 | "hashes": [ 146 | "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", 147 | "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", 148 | "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", 149 | "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", 150 | "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", 151 | "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", 152 | "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", 153 | "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", 154 | "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0", 155 | "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448", 156 | "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", 157 | "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", 158 | "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", 159 | "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", 160 | "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", 161 | "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", 162 | "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59", 163 | "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23", 164 | "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", 165 | "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", 166 | "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", 167 | "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", 168 | "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", 169 | "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", 170 | "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", 171 | "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", 172 | "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974", 173 | "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", 174 | "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", 175 | "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", 176 | "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", 177 | "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8", 178 | "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", 179 | "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", 180 | "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", 181 | "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", 182 | "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", 183 | "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", 184 | "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", 185 | "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", 186 | "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", 187 | "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", 188 | "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", 189 | "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b", 190 | "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", 191 | "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", 192 | "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", 193 | "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1", 194 | "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", 195 | "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", 196 | "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", 197 | "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", 198 | "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", 199 | "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", 200 | "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", 201 | "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", 202 | "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909", 203 | "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", 204 | "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", 205 | "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", 206 | "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755", 207 | "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", 208 | "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", 209 | "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", 210 | "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", 211 | "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", 212 | "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", 213 | "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", 214 | "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", 215 | "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", 216 | "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", 217 | "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", 218 | "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", 219 | "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", 220 | "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" 221 | ], 222 | "markers": "python_full_version >= '3.7.0'", 223 | "version": "==3.1.0" 224 | }, 225 | "docker": { 226 | "hashes": [ 227 | "sha256:896c4282e5c7af5c45e8b683b0b0c33932974fe6e50fc6906a0a83616ab3da97", 228 | "sha256:dbcb3bd2fa80dca0788ed908218bf43972772009b881ed1e20dfc29a65e49782" 229 | ], 230 | "index": "pypi", 231 | "version": "==6.0.1" 232 | }, 233 | "frozenlist": { 234 | "hashes": [ 235 | "sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c", 236 | "sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f", 237 | "sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a", 238 | "sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784", 239 | "sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27", 240 | "sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d", 241 | "sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3", 242 | "sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678", 243 | "sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a", 244 | "sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483", 245 | "sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8", 246 | "sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf", 247 | "sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99", 248 | "sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c", 249 | "sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48", 250 | "sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5", 251 | "sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56", 252 | "sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e", 253 | "sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1", 254 | "sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401", 255 | "sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4", 256 | "sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e", 257 | "sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649", 258 | "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a", 259 | "sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d", 260 | "sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0", 261 | "sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6", 262 | "sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d", 263 | "sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b", 264 | "sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6", 265 | "sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf", 266 | "sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef", 267 | "sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7", 268 | "sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842", 269 | "sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba", 270 | "sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420", 271 | "sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b", 272 | "sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d", 273 | "sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332", 274 | "sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936", 275 | "sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816", 276 | "sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91", 277 | "sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420", 278 | "sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448", 279 | "sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411", 280 | "sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4", 281 | "sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32", 282 | "sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b", 283 | "sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0", 284 | "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530", 285 | "sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669", 286 | "sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7", 287 | "sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1", 288 | "sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5", 289 | "sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce", 290 | "sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4", 291 | "sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e", 292 | "sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2", 293 | "sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d", 294 | "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9", 295 | "sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642", 296 | "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0", 297 | "sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703", 298 | "sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb", 299 | "sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1", 300 | "sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13", 301 | "sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab", 302 | "sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38", 303 | "sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb", 304 | "sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb", 305 | "sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81", 306 | "sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8", 307 | "sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd", 308 | "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4" 309 | ], 310 | "markers": "python_version >= '3.7'", 311 | "version": "==1.3.3" 312 | }, 313 | "idna": { 314 | "hashes": [ 315 | "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", 316 | "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" 317 | ], 318 | "markers": "python_version >= '3.5'", 319 | "version": "==3.4" 320 | }, 321 | "jinja2": { 322 | "hashes": [ 323 | "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", 324 | "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" 325 | ], 326 | "index": "pypi", 327 | "version": "==3.1.2" 328 | }, 329 | "markupsafe": { 330 | "hashes": [ 331 | "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed", 332 | "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc", 333 | "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2", 334 | "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460", 335 | "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7", 336 | "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0", 337 | "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1", 338 | "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa", 339 | "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03", 340 | "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323", 341 | "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65", 342 | "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013", 343 | "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036", 344 | "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f", 345 | "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4", 346 | "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419", 347 | "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2", 348 | "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619", 349 | "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a", 350 | "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a", 351 | "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd", 352 | "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7", 353 | "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666", 354 | "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65", 355 | "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859", 356 | "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625", 357 | "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff", 358 | "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156", 359 | "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd", 360 | "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba", 361 | "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f", 362 | "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1", 363 | "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094", 364 | "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a", 365 | "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513", 366 | "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed", 367 | "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d", 368 | "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3", 369 | "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147", 370 | "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c", 371 | "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603", 372 | "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601", 373 | "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a", 374 | "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1", 375 | "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d", 376 | "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3", 377 | "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54", 378 | "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2", 379 | "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6", 380 | "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58" 381 | ], 382 | "markers": "python_version >= '3.7'", 383 | "version": "==2.1.2" 384 | }, 385 | "multidict": { 386 | "hashes": [ 387 | "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9", 388 | "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8", 389 | "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03", 390 | "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710", 391 | "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161", 392 | "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664", 393 | "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569", 394 | "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067", 395 | "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313", 396 | "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706", 397 | "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2", 398 | "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636", 399 | "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49", 400 | "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93", 401 | "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603", 402 | "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0", 403 | "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60", 404 | "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4", 405 | "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e", 406 | "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1", 407 | "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60", 408 | "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951", 409 | "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc", 410 | "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe", 411 | "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95", 412 | "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d", 413 | "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8", 414 | "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed", 415 | "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2", 416 | "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775", 417 | "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87", 418 | "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c", 419 | "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2", 420 | "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98", 421 | "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3", 422 | "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe", 423 | "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78", 424 | "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660", 425 | "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176", 426 | "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e", 427 | "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988", 428 | "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c", 429 | "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c", 430 | "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0", 431 | "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449", 432 | "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f", 433 | "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde", 434 | "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5", 435 | "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d", 436 | "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac", 437 | "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a", 438 | "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9", 439 | "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca", 440 | "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11", 441 | "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35", 442 | "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063", 443 | "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b", 444 | "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982", 445 | "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258", 446 | "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1", 447 | "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52", 448 | "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480", 449 | "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7", 450 | "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461", 451 | "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d", 452 | "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc", 453 | "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779", 454 | "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a", 455 | "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547", 456 | "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0", 457 | "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171", 458 | "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf", 459 | "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d", 460 | "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba" 461 | ], 462 | "markers": "python_version >= '3.7'", 463 | "version": "==6.0.4" 464 | }, 465 | "openai": { 466 | "hashes": [ 467 | "sha256:3b82c867d531e1fd2003d9de2131e1c4bfd4c70b1a3149e0543a555b30807b70", 468 | "sha256:9f9d27d26e62c6068f516c0729449954b5ef6994be1a6cbfe7dbefbc84423a04" 469 | ], 470 | "index": "pypi", 471 | "version": "==0.27.4" 472 | }, 473 | "packaging": { 474 | "hashes": [ 475 | "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", 476 | "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97" 477 | ], 478 | "markers": "python_version >= '3.7'", 479 | "version": "==23.0" 480 | }, 481 | "requests": { 482 | "hashes": [ 483 | "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", 484 | "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" 485 | ], 486 | "markers": "python_version >= '3.7' and python_version < '4'", 487 | "version": "==2.28.2" 488 | }, 489 | "structlog": { 490 | "hashes": [ 491 | "sha256:270d681dd7d163c11ba500bc914b2472d2b50a8ef00faa999ded5ff83a2f906b", 492 | "sha256:79b9e68e48b54e373441e130fa447944e6f87a05b35de23138e475c05d0f7e0e" 493 | ], 494 | "index": "pypi", 495 | "version": "==23.1.0" 496 | }, 497 | "termcolor": { 498 | "hashes": [ 499 | "sha256:91ddd848e7251200eac969846cbae2dacd7d71c2871e92733289e7e3666f48e7", 500 | "sha256:dfc8ac3f350788f23b2947b3e6cfa5a53b630b612e6cd8965a015a776020b99a" 501 | ], 502 | "index": "pypi", 503 | "version": "==2.2.0" 504 | }, 505 | "tqdm": { 506 | "hashes": [ 507 | "sha256:1871fb68a86b8fb3b59ca4cdd3dcccbc7e6d613eeed31f4c332531977b89beb5", 508 | "sha256:c4f53a17fe37e132815abceec022631be8ffe1b9381c2e6e30aa70edc99e9671" 509 | ], 510 | "markers": "python_version >= '3.7'", 511 | "version": "==4.65.0" 512 | }, 513 | "urllib3": { 514 | "hashes": [ 515 | "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305", 516 | "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42" 517 | ], 518 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 519 | "version": "==1.26.15" 520 | }, 521 | "websocket-client": { 522 | "hashes": [ 523 | "sha256:3f09e6d8230892547132177f575a4e3e73cfdf06526e20cc02aa1c3b47184d40", 524 | "sha256:cdf5877568b7e83aa7cf2244ab56a3213de587bbe0ce9d8b9600fc77b455d89e" 525 | ], 526 | "markers": "python_version >= '3.7'", 527 | "version": "==1.5.1" 528 | }, 529 | "yarl": { 530 | "hashes": [ 531 | "sha256:009a028127e0a1755c38b03244c0bea9d5565630db9c4cf9572496e947137a87", 532 | "sha256:0414fd91ce0b763d4eadb4456795b307a71524dbacd015c657bb2a39db2eab89", 533 | "sha256:0978f29222e649c351b173da2b9b4665ad1feb8d1daa9d971eb90df08702668a", 534 | "sha256:0ef8fb25e52663a1c85d608f6dd72e19bd390e2ecaf29c17fb08f730226e3a08", 535 | "sha256:10b08293cda921157f1e7c2790999d903b3fd28cd5c208cf8826b3b508026996", 536 | "sha256:1684a9bd9077e922300ecd48003ddae7a7474e0412bea38d4631443a91d61077", 537 | "sha256:1b372aad2b5f81db66ee7ec085cbad72c4da660d994e8e590c997e9b01e44901", 538 | "sha256:1e21fb44e1eff06dd6ef971d4bdc611807d6bd3691223d9c01a18cec3677939e", 539 | "sha256:2305517e332a862ef75be8fad3606ea10108662bc6fe08509d5ca99503ac2aee", 540 | "sha256:24ad1d10c9db1953291f56b5fe76203977f1ed05f82d09ec97acb623a7976574", 541 | "sha256:272b4f1599f1b621bf2aabe4e5b54f39a933971f4e7c9aa311d6d7dc06965165", 542 | "sha256:2a1fca9588f360036242f379bfea2b8b44cae2721859b1c56d033adfd5893634", 543 | "sha256:2b4fa2606adf392051d990c3b3877d768771adc3faf2e117b9de7eb977741229", 544 | "sha256:3150078118f62371375e1e69b13b48288e44f6691c1069340081c3fd12c94d5b", 545 | "sha256:326dd1d3caf910cd26a26ccbfb84c03b608ba32499b5d6eeb09252c920bcbe4f", 546 | "sha256:34c09b43bd538bf6c4b891ecce94b6fa4f1f10663a8d4ca589a079a5018f6ed7", 547 | "sha256:388a45dc77198b2460eac0aca1efd6a7c09e976ee768b0d5109173e521a19daf", 548 | "sha256:3adeef150d528ded2a8e734ebf9ae2e658f4c49bf413f5f157a470e17a4a2e89", 549 | "sha256:3edac5d74bb3209c418805bda77f973117836e1de7c000e9755e572c1f7850d0", 550 | "sha256:3f6b4aca43b602ba0f1459de647af954769919c4714706be36af670a5f44c9c1", 551 | "sha256:3fc056e35fa6fba63248d93ff6e672c096f95f7836938241ebc8260e062832fe", 552 | "sha256:418857f837347e8aaef682679f41e36c24250097f9e2f315d39bae3a99a34cbf", 553 | "sha256:42430ff511571940d51e75cf42f1e4dbdded477e71c1b7a17f4da76c1da8ea76", 554 | "sha256:44ceac0450e648de86da8e42674f9b7077d763ea80c8ceb9d1c3e41f0f0a9951", 555 | "sha256:47d49ac96156f0928f002e2424299b2c91d9db73e08c4cd6742923a086f1c863", 556 | "sha256:48dd18adcf98ea9cd721a25313aef49d70d413a999d7d89df44f469edfb38a06", 557 | "sha256:49d43402c6e3013ad0978602bf6bf5328535c48d192304b91b97a3c6790b1562", 558 | "sha256:4d04acba75c72e6eb90745447d69f84e6c9056390f7a9724605ca9c56b4afcc6", 559 | "sha256:57a7c87927a468e5a1dc60c17caf9597161d66457a34273ab1760219953f7f4c", 560 | "sha256:58a3c13d1c3005dbbac5c9f0d3210b60220a65a999b1833aa46bd6677c69b08e", 561 | "sha256:5df5e3d04101c1e5c3b1d69710b0574171cc02fddc4b23d1b2813e75f35a30b1", 562 | "sha256:63243b21c6e28ec2375f932a10ce7eda65139b5b854c0f6b82ed945ba526bff3", 563 | "sha256:64dd68a92cab699a233641f5929a40f02a4ede8c009068ca8aa1fe87b8c20ae3", 564 | "sha256:6604711362f2dbf7160df21c416f81fac0de6dbcf0b5445a2ef25478ecc4c778", 565 | "sha256:6c4fcfa71e2c6a3cb568cf81aadc12768b9995323186a10827beccf5fa23d4f8", 566 | "sha256:6d88056a04860a98341a0cf53e950e3ac9f4e51d1b6f61a53b0609df342cc8b2", 567 | "sha256:705227dccbe96ab02c7cb2c43e1228e2826e7ead880bb19ec94ef279e9555b5b", 568 | "sha256:728be34f70a190566d20aa13dc1f01dc44b6aa74580e10a3fb159691bc76909d", 569 | "sha256:74dece2bfc60f0f70907c34b857ee98f2c6dd0f75185db133770cd67300d505f", 570 | "sha256:75c16b2a900b3536dfc7014905a128a2bea8fb01f9ee26d2d7d8db0a08e7cb2c", 571 | "sha256:77e913b846a6b9c5f767b14dc1e759e5aff05502fe73079f6f4176359d832581", 572 | "sha256:7a66c506ec67eb3159eea5096acd05f5e788ceec7b96087d30c7d2865a243918", 573 | "sha256:8c46d3d89902c393a1d1e243ac847e0442d0196bbd81aecc94fcebbc2fd5857c", 574 | "sha256:93202666046d9edadfe9f2e7bf5e0782ea0d497b6d63da322e541665d65a044e", 575 | "sha256:97209cc91189b48e7cfe777237c04af8e7cc51eb369004e061809bcdf4e55220", 576 | "sha256:a48f4f7fea9a51098b02209d90297ac324241bf37ff6be6d2b0149ab2bd51b37", 577 | "sha256:a783cd344113cb88c5ff7ca32f1f16532a6f2142185147822187913eb989f739", 578 | "sha256:ae0eec05ab49e91a78700761777f284c2df119376e391db42c38ab46fd662b77", 579 | "sha256:ae4d7ff1049f36accde9e1ef7301912a751e5bae0a9d142459646114c70ecba6", 580 | "sha256:b05df9ea7496df11b710081bd90ecc3a3db6adb4fee36f6a411e7bc91a18aa42", 581 | "sha256:baf211dcad448a87a0d9047dc8282d7de59473ade7d7fdf22150b1d23859f946", 582 | "sha256:bb81f753c815f6b8e2ddd2eef3c855cf7da193b82396ac013c661aaa6cc6b0a5", 583 | "sha256:bcd7bb1e5c45274af9a1dd7494d3c52b2be5e6bd8d7e49c612705fd45420b12d", 584 | "sha256:bf071f797aec5b96abfc735ab97da9fd8f8768b43ce2abd85356a3127909d146", 585 | "sha256:c15163b6125db87c8f53c98baa5e785782078fbd2dbeaa04c6141935eb6dab7a", 586 | "sha256:cb6d48d80a41f68de41212f3dfd1a9d9898d7841c8f7ce6696cf2fd9cb57ef83", 587 | "sha256:ceff9722e0df2e0a9e8a79c610842004fa54e5b309fe6d218e47cd52f791d7ef", 588 | "sha256:cfa2bbca929aa742b5084fd4663dd4b87c191c844326fcb21c3afd2d11497f80", 589 | "sha256:d617c241c8c3ad5c4e78a08429fa49e4b04bedfc507b34b4d8dceb83b4af3588", 590 | "sha256:d881d152ae0007809c2c02e22aa534e702f12071e6b285e90945aa3c376463c5", 591 | "sha256:da65c3f263729e47351261351b8679c6429151ef9649bba08ef2528ff2c423b2", 592 | "sha256:de986979bbd87272fe557e0a8fcb66fd40ae2ddfe28a8b1ce4eae22681728fef", 593 | "sha256:df60a94d332158b444301c7f569659c926168e4d4aad2cfbf4bce0e8fb8be826", 594 | "sha256:dfef7350ee369197106805e193d420b75467b6cceac646ea5ed3049fcc950a05", 595 | "sha256:e59399dda559688461762800d7fb34d9e8a6a7444fd76ec33220a926c8be1516", 596 | "sha256:e6f3515aafe0209dd17fb9bdd3b4e892963370b3de781f53e1746a521fb39fc0", 597 | "sha256:e7fd20d6576c10306dea2d6a5765f46f0ac5d6f53436217913e952d19237efc4", 598 | "sha256:ebb78745273e51b9832ef90c0898501006670d6e059f2cdb0e999494eb1450c2", 599 | "sha256:efff27bd8cbe1f9bd127e7894942ccc20c857aa8b5a0327874f30201e5ce83d0", 600 | "sha256:f37db05c6051eff17bc832914fe46869f8849de5b92dc4a3466cd63095d23dfd", 601 | "sha256:f8ca8ad414c85bbc50f49c0a106f951613dfa5f948ab69c10ce9b128d368baf8", 602 | "sha256:fb742dcdd5eec9f26b61224c23baea46c9055cf16f62475e11b9b15dfd5c117b", 603 | "sha256:fc77086ce244453e074e445104f0ecb27530d6fd3a46698e33f6c38951d5a0f1", 604 | "sha256:ff205b58dc2929191f68162633d5e10e8044398d7a45265f90a0f1d51f85f72c" 605 | ], 606 | "markers": "python_version >= '3.7'", 607 | "version": "==1.8.2" 608 | } 609 | }, 610 | "develop": { 611 | "black": { 612 | "hashes": [ 613 | "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5", 614 | "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915", 615 | "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326", 616 | "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940", 617 | "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b", 618 | "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30", 619 | "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c", 620 | "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c", 621 | "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab", 622 | "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27", 623 | "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2", 624 | "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961", 625 | "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9", 626 | "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb", 627 | "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70", 628 | "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331", 629 | "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2", 630 | "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266", 631 | "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d", 632 | "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6", 633 | "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b", 634 | "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925", 635 | "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8", 636 | "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4", 637 | "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3" 638 | ], 639 | "index": "pypi", 640 | "version": "==23.3.0" 641 | }, 642 | "bleach": { 643 | "hashes": [ 644 | "sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414", 645 | "sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4" 646 | ], 647 | "markers": "python_version >= '3.7'", 648 | "version": "==6.0.0" 649 | }, 650 | "certifi": { 651 | "hashes": [ 652 | "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3", 653 | "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18" 654 | ], 655 | "markers": "python_version >= '3.6'", 656 | "version": "==2022.12.7" 657 | }, 658 | "charset-normalizer": { 659 | "hashes": [ 660 | "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", 661 | "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", 662 | "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", 663 | "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", 664 | "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", 665 | "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", 666 | "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", 667 | "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", 668 | "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0", 669 | "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448", 670 | "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", 671 | "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", 672 | "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", 673 | "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", 674 | "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", 675 | "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", 676 | "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59", 677 | "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23", 678 | "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", 679 | "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", 680 | "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", 681 | "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", 682 | "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", 683 | "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", 684 | "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", 685 | "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", 686 | "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974", 687 | "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", 688 | "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", 689 | "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", 690 | "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", 691 | "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8", 692 | "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", 693 | "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", 694 | "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", 695 | "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", 696 | "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", 697 | "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", 698 | "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", 699 | "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", 700 | "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", 701 | "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", 702 | "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", 703 | "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b", 704 | "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", 705 | "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", 706 | "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", 707 | "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1", 708 | "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", 709 | "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", 710 | "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", 711 | "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", 712 | "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", 713 | "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", 714 | "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", 715 | "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", 716 | "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909", 717 | "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", 718 | "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", 719 | "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", 720 | "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755", 721 | "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", 722 | "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", 723 | "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", 724 | "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", 725 | "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", 726 | "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", 727 | "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", 728 | "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", 729 | "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", 730 | "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", 731 | "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", 732 | "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", 733 | "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", 734 | "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" 735 | ], 736 | "markers": "python_full_version >= '3.7.0'", 737 | "version": "==3.1.0" 738 | }, 739 | "click": { 740 | "hashes": [ 741 | "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", 742 | "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" 743 | ], 744 | "markers": "python_version >= '3.7'", 745 | "version": "==8.1.3" 746 | }, 747 | "docutils": { 748 | "hashes": [ 749 | "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6", 750 | "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc" 751 | ], 752 | "markers": "python_version >= '3.7'", 753 | "version": "==0.19" 754 | }, 755 | "idna": { 756 | "hashes": [ 757 | "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", 758 | "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" 759 | ], 760 | "markers": "python_version >= '3.5'", 761 | "version": "==3.4" 762 | }, 763 | "importlib-metadata": { 764 | "hashes": [ 765 | "sha256:23c2bcae4762dfb0bbe072d358faec24957901d75b6c4ab11172c0c982532402", 766 | "sha256:8f8bd2af397cf33bd344d35cfe7f489219b7d14fc79a3f854b75b8417e9226b0" 767 | ], 768 | "markers": "python_version >= '3.7'", 769 | "version": "==6.3.0" 770 | }, 771 | "jaraco.classes": { 772 | "hashes": [ 773 | "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158", 774 | "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a" 775 | ], 776 | "markers": "python_version >= '3.7'", 777 | "version": "==3.2.3" 778 | }, 779 | "keyring": { 780 | "hashes": [ 781 | "sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd", 782 | "sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678" 783 | ], 784 | "markers": "python_version >= '3.7'", 785 | "version": "==23.13.1" 786 | }, 787 | "markdown-it-py": { 788 | "hashes": [ 789 | "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30", 790 | "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1" 791 | ], 792 | "markers": "python_version >= '3.7'", 793 | "version": "==2.2.0" 794 | }, 795 | "mdurl": { 796 | "hashes": [ 797 | "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", 798 | "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" 799 | ], 800 | "markers": "python_version >= '3.7'", 801 | "version": "==0.1.2" 802 | }, 803 | "more-itertools": { 804 | "hashes": [ 805 | "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d", 806 | "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3" 807 | ], 808 | "markers": "python_version >= '3.7'", 809 | "version": "==9.1.0" 810 | }, 811 | "mypy-extensions": { 812 | "hashes": [ 813 | "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", 814 | "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" 815 | ], 816 | "markers": "python_version >= '3.5'", 817 | "version": "==1.0.0" 818 | }, 819 | "packaging": { 820 | "hashes": [ 821 | "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", 822 | "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97" 823 | ], 824 | "markers": "python_version >= '3.7'", 825 | "version": "==23.0" 826 | }, 827 | "pathspec": { 828 | "hashes": [ 829 | "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687", 830 | "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293" 831 | ], 832 | "markers": "python_version >= '3.7'", 833 | "version": "==0.11.1" 834 | }, 835 | "pkginfo": { 836 | "hashes": [ 837 | "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546", 838 | "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046" 839 | ], 840 | "markers": "python_version >= '3.6'", 841 | "version": "==1.9.6" 842 | }, 843 | "platformdirs": { 844 | "hashes": [ 845 | "sha256:d5b638ca397f25f979350ff789db335903d7ea010ab28903f57b27e1b16c2b08", 846 | "sha256:ebe11c0d7a805086e99506aa331612429a72ca7cd52a1f0d277dc4adc20cb10e" 847 | ], 848 | "markers": "python_version >= '3.7'", 849 | "version": "==3.2.0" 850 | }, 851 | "pygments": { 852 | "hashes": [ 853 | "sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094", 854 | "sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500" 855 | ], 856 | "markers": "python_version >= '3.7'", 857 | "version": "==2.15.0" 858 | }, 859 | "readme-renderer": { 860 | "hashes": [ 861 | "sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273", 862 | "sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343" 863 | ], 864 | "markers": "python_version >= '3.7'", 865 | "version": "==37.3" 866 | }, 867 | "requests": { 868 | "hashes": [ 869 | "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa", 870 | "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf" 871 | ], 872 | "markers": "python_version >= '3.7' and python_version < '4'", 873 | "version": "==2.28.2" 874 | }, 875 | "requests-toolbelt": { 876 | "hashes": [ 877 | "sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7", 878 | "sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d" 879 | ], 880 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 881 | "version": "==0.10.1" 882 | }, 883 | "rfc3986": { 884 | "hashes": [ 885 | "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", 886 | "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c" 887 | ], 888 | "markers": "python_version >= '3.7'", 889 | "version": "==2.0.0" 890 | }, 891 | "rich": { 892 | "hashes": [ 893 | "sha256:540c7d6d26a1178e8e8b37e9ba44573a3cd1464ff6348b99ee7061b95d1c6333", 894 | "sha256:dc84400a9d842b3a9c5ff74addd8eb798d155f36c1c91303888e0a66850d2a15" 895 | ], 896 | "markers": "python_full_version >= '3.7.0'", 897 | "version": "==13.3.3" 898 | }, 899 | "six": { 900 | "hashes": [ 901 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 902 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 903 | ], 904 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 905 | "version": "==1.16.0" 906 | }, 907 | "tomli": { 908 | "hashes": [ 909 | "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", 910 | "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" 911 | ], 912 | "markers": "python_version < '3.11'", 913 | "version": "==2.0.1" 914 | }, 915 | "twine": { 916 | "hashes": [ 917 | "sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8", 918 | "sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8" 919 | ], 920 | "index": "pypi", 921 | "version": "==4.0.2" 922 | }, 923 | "urllib3": { 924 | "hashes": [ 925 | "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305", 926 | "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42" 927 | ], 928 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 929 | "version": "==1.26.15" 930 | }, 931 | "webencodings": { 932 | "hashes": [ 933 | "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", 934 | "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" 935 | ], 936 | "version": "==0.5.1" 937 | }, 938 | "zipp": { 939 | "hashes": [ 940 | "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b", 941 | "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556" 942 | ], 943 | "markers": "python_version >= '3.7'", 944 | "version": "==3.15.0" 945 | } 946 | } 947 | } 948 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `botsh` 2 | 3 | [![PyPI version](https://badge.fury.io/py/botsh.svg)](https://badge.fury.io/py/botsh) 4 | 5 | `botsh` attaches a chat agent to a Docker container running Ubuntu. 6 | 7 | This effectively gives the agent access to the entire [APT](https://wiki.debian.org/Apt) 8 | universe, while limiting its blast radius to the current directory (which gets mounted into the container). 9 | 10 | ## Demo 11 | 12 | This demo uses the prompt `create a sequence of 100 200x200 images which each contain a different number, and turn them into a 30fps video with ffmpeg`. 13 | 14 | `botsh`: 15 | 16 | - tries to run `imagemagick` and fails (because it is a fresh Ubuntu install) 17 | - installs `imagemagick` 18 | - attempts to run `ffmpeg` and fails 19 | - installs `ffmpeg` 20 | - attempts to turn frames into a video and fails (because it has not created them) 21 | - writes a bash for-loop to generate 100 frames with `imagemagick` 22 | - uses `ffmpeg` to convert them into a video 23 | 24 | https://user-images.githubusercontent.com/46173/230953506-c8545345-c0a1-46b1-b937-458191fc2456.mp4 25 | 26 | ## Setup 27 | 28 | Install with: 29 | 30 | pip install botsh 31 | 32 | `botsh` expects an OpenAI API key to be provided as the `OPENAI_API_KEY` 33 | environment variable. 34 | 35 | `botsh` also requires Docker to be running on the system. 36 | 37 | ## Examples 38 | 39 | botsh "convert cat.jpg into a png file" 40 | 41 | botsh "use a remote service to find my public ip and base64 encode it" 42 | 43 | botsh "run pylint on the codebase in src/" 44 | 45 | ## Additional details 46 | 47 | `botsh` will create a bare Ubuntu Docker container associated with 48 | the current directory, or create one if one does not exist. botsh 49 | will then attach the OpenAI API to a shell running in the container 50 | to attempt to complete the given task. 51 | 52 | The AI is explicitly told that it is allowed to install software, 53 | and will typically install programs as needed to complete its task. 54 | Installed software remains confined to the container. 55 | 56 | ## Observations 57 | 58 | These observations relate to the default model, `text-davinci-003`. Using GPT-4 may improve things. 59 | 60 | - It works best if you explicitly specify the files/paths you want to work with (use relative references). 61 | It is not good at figuring out what you mean. 62 | - It often gets stuck in loops if it can't complete a task rather than giving up, despite the prompt 63 | telling it not to. 64 | - It sometimes needs subtle encouragement to break a task down into multiple parts, instead of chaining together 65 | a long shell command, particularly when the command it wants to run has a bug. For example, instead of 66 | saying “convert foo.png to a gif and compute its md5 sum”, try “convert foo.png into a gif, and then compute 67 | its md5 sum” 68 | 69 | ## Container re-use 70 | 71 | When `botsh` is invoked, the current working directory is mounted 72 | into the container and can be modified by programs the agent runs. 73 | The filesystem outside of the current working directory is sealed 74 | off from the container. 75 | 76 | Each directory that you run `botsh` in will get its own container, 77 | which is reused for future invocations of `botsh` in that container. 78 | 79 | You can pass `--wipe` to discard the existing container and start a 80 | new one before running your task. You can also pass `--rm` to remove 81 | a container at the end of your task. 82 | 83 | Containers are also removed if you purge containers in Docker with 84 | `docker container prune` 85 | 86 | ## Usage 87 | 88 | ``` 89 | usage: botsh [-h] [--max-rounds MAX_ROUNDS] [--model MODEL] [--image IMAGE] [--shell-command SHELL_COMMAND] [--save-transcript] [--wipe] prompt 90 | 91 | Task runner powered by OpenAI and Docker. Invoke botsh by providing a task as a command line argument. botsh will create a bare Ubuntu Docker container associated with the current directory, or create one if one does not exist. botsh will then attach the OpenAI API to a shell running in the container to attempt to complete the given task. 92 | 93 | positional arguments: 94 | prompt Prompt to execute. 95 | 96 | options: 97 | -h, --help show this help message and exit 98 | --max-rounds MAX_ROUNDS 99 | --model MODEL OpenAI text completion model to use. 100 | --image IMAGE Docker image to use.The current hard-coded prompt works for Debian-derived distributions. 101 | --shell-command SHELL_COMMAND 102 | Shell to invoke within the container. 103 | --save-transcript Save transcript to file 104 | --wipe Start with a fresh container even if one exists for this directory. 105 | ``` 106 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import json 2 | from setuptools import setup, find_packages 3 | 4 | with open("README.md", "r", encoding="utf-8") as fh: 5 | long_description = fh.read() 6 | 7 | 8 | def load_requirements_from_pipfile_lock(dev: bool): 9 | with open("Pipfile.lock") as f: 10 | pipfile_lock = json.load(f) 11 | 12 | if dev: 13 | mode = "develop" 14 | else: 15 | mode = "default" 16 | 17 | return [ 18 | f"{pkg_name}{pkg_info['version']}" 19 | for pkg_name, pkg_info in pipfile_lock[mode].items() 20 | ] 21 | 22 | 23 | setup( 24 | name="botsh", 25 | version="0.1.7", 26 | description="A task runner powered by OpenAI and Docker.", 27 | long_description=long_description, 28 | long_description_content_type="text/markdown", 29 | author="Paul Butler", 30 | author_email="paul@driftingin.space", 31 | url="https://github.com/drifting-in-space/botsh", 32 | packages=find_packages("src"), 33 | package_data={ 34 | "": ["Pipfile", "Pipfile.lock"], 35 | }, 36 | include_package_data=True, 37 | package_dir={"": "src"}, 38 | classifiers=[ 39 | "Intended Audience :: Developers", 40 | "License :: OSI Approved :: MIT License", 41 | "Programming Language :: Python :: 3", 42 | "Programming Language :: Python :: 3.6", 43 | "Programming Language :: Python :: 3.7", 44 | "Programming Language :: Python :: 3.8", 45 | "Programming Language :: Python :: 3.9", 46 | ], 47 | python_requires=">=3.6", 48 | install_requires=load_requirements_from_pipfile_lock(False), 49 | extras_require={ 50 | "dev": load_requirements_from_pipfile_lock(True), 51 | }, 52 | entry_points={ 53 | "console_scripts": [ 54 | "botsh=botsh.main:main", 55 | ], 56 | }, 57 | ) 58 | -------------------------------------------------------------------------------- /src/botsh/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamsocket/botsh/3f5411601628a014e5ba39f2cb2e80f5640e7618/src/botsh/__init__.py -------------------------------------------------------------------------------- /src/botsh/docker_exec.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import os 3 | import shlex 4 | 5 | import docker 6 | from docker.types import Mount 7 | from termcolor import colored 8 | import platform 9 | 10 | from botsh.logging import log 11 | 12 | 13 | class DockerContainer: 14 | current_directory: str 15 | shell_command: str 16 | remove_on_exit: bool 17 | 18 | def __init__( 19 | self, image: str, shell_command: str, wipe: bool, remove_on_exit: bool 20 | ): 21 | self.remove_on_exit = remove_on_exit 22 | self.current_directory = os.getcwd() 23 | self.shell_command = shell_command 24 | dir_hash = hashlib.sha256(self.current_directory.encode("utf-8")).hexdigest() 25 | container_name = f"botsh-{dir_hash}" 26 | 27 | log.info("Connecting to Docker...") 28 | try: 29 | self.client = docker.from_env() 30 | except Exception as e: 31 | log.error("Error connecting to Docker. Is Docker running?", err=e) 32 | if platform.system() == "Linux": 33 | log.info( 34 | "You may need to follow these instructions: " 35 | "https://docs.docker.com/engine/install/linux-postinstall/." 36 | ) 37 | exit(1) 38 | 39 | self._get_container(container_name, image, wipe) 40 | 41 | def _get_mounts(self) -> list[Mount]: 42 | mount = Mount(target="/work", source=self.current_directory, type="bind") 43 | 44 | return [mount] 45 | 46 | def _get_container(self, container_name: str, image: str, wipe: bool): 47 | try: 48 | log.info("Locating existing container...") 49 | container = self.client.containers.get(container_name) 50 | if not wipe: 51 | log.info("Using existing container.") 52 | if container.status != "running": 53 | log.info("Starting container...") 54 | container.start() 55 | self.container = container 56 | return 57 | else: 58 | log.info("Terminating existing container.") 59 | container.stop(timeout=0) 60 | container.remove(force=True) 61 | except docker.errors.NotFound: 62 | log.info("No container exists, creating one.") 63 | 64 | log.info("Pulling image...") 65 | self.client.images.pull(image) 66 | 67 | mounts = self._get_mounts() 68 | 69 | log.info("Creating container...") 70 | container = self.client.containers.create( 71 | image, 72 | name=container_name, 73 | command=self.shell_command, 74 | tty=True, 75 | mounts=mounts, 76 | environment=["DEBIAN_FRONTEND=noninteractive"], 77 | ) 78 | log.info("Starting container...") 79 | container.start() 80 | log.info("Updating apt-get...") 81 | self.container = container 82 | self.run_command("apt-get -qq update") 83 | 84 | def run_command(self, command: str, quiet: bool = False) -> tuple[int, str]: 85 | quoted_command = shlex.quote(command) 86 | 87 | exec = self.client.api.exec_create( 88 | self.container.id, 89 | f"{self.shell_command} -c {quoted_command}", 90 | workdir="/work", 91 | ) 92 | exec_id = exec["Id"] 93 | 94 | output = self.client.api.exec_start(exec_id, stream=True) 95 | 96 | result = [] 97 | for line in output: 98 | line = line.decode("utf-8") 99 | if not quiet: 100 | print(colored(line, "green"), end="") 101 | result.append(line) 102 | 103 | exit_code = self.client.api.exec_inspect(exec_id)["ExitCode"] 104 | 105 | return exit_code, "".join(result) 106 | 107 | def __del__(self): 108 | if hasattr(self, "container"): 109 | log.info("Terminating container.") 110 | self.container.stop(timeout=0) 111 | if self.remove_on_exit: 112 | log.info("Removing container.") 113 | self.container.remove() 114 | -------------------------------------------------------------------------------- /src/botsh/history.py: -------------------------------------------------------------------------------- 1 | class CommandExecution: 2 | explanation: str 3 | command: str 4 | result: str 5 | exit_code: int 6 | 7 | def __init__(self, explanation: str, command: str, result: str, exit_code: int): 8 | self.explanation = explanation 9 | self.command = command 10 | self.result = result 11 | self.exit_code = exit_code 12 | -------------------------------------------------------------------------------- /src/botsh/llm.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import datetime 3 | 4 | import openai 5 | 6 | from botsh.history import CommandExecution 7 | from botsh.prompt import generate_prompt 8 | 9 | 10 | class LLM: 11 | def __init__(self, model, save_transcript: bool = False): 12 | self.model = model 13 | self.save_transcript = save_transcript 14 | self.count = 0 15 | self.session_time = datetime.now().strftime("%Y%m%d-%H%M%S") 16 | 17 | def completion(self, task: str, history: list[CommandExecution]): 18 | prompt = generate_prompt(task, history) 19 | transcript_dirname = os.path.join( 20 | os.getcwd(), "transcripts", self.session_time, str(self.count) 21 | ) 22 | 23 | if self.save_transcript: 24 | os.makedirs(transcript_dirname, exist_ok=True) 25 | with open( 26 | os.path.join(transcript_dirname, "request.txt"), "w", encoding="utf-8" 27 | ) as f: 28 | f.write(prompt) 29 | self.count += 1 30 | 31 | response = self._completion(prompt) 32 | 33 | if self.save_transcript: 34 | with open( 35 | os.path.join(transcript_dirname, "response.txt"), "w", encoding="utf-8" 36 | ) as f: 37 | f.write(response) 38 | 39 | return response 40 | 41 | def _completion(self, prompt): 42 | response = openai.Completion.create( 43 | engine=self.model, 44 | prompt=prompt, 45 | temperature=0.0, 46 | max_tokens=100, 47 | top_p=1, 48 | frequency_penalty=0, 49 | presence_penalty=0, 50 | stop=["DONE", "OUTPUT:", "EXPLANATION:", "EXIT_CODE:"], 51 | ) 52 | 53 | return response.choices[0].text 54 | -------------------------------------------------------------------------------- /src/botsh/logging.py: -------------------------------------------------------------------------------- 1 | import structlog 2 | log = structlog.get_logger() 3 | -------------------------------------------------------------------------------- /src/botsh/main.py: -------------------------------------------------------------------------------- 1 | """Task runner powered by OpenAI and Docker. 2 | 3 | Invoke botsh by providing a task as a command line argument. 4 | 5 | botsh will create a bare Ubuntu Docker container associated with 6 | the current directory, or create one if one does not exist. botsh 7 | will then attach the OpenAI API to a shell running in the container 8 | to attempt to complete the given task. 9 | """ 10 | 11 | import argparse 12 | from os import environ 13 | 14 | from botsh.docker_exec import DockerContainer 15 | from botsh.llm import LLM 16 | from botsh.logging import log 17 | from botsh.task_driver import TaskDriver 18 | 19 | 20 | def main(): 21 | parser = argparse.ArgumentParser(description=__doc__) 22 | parser.add_argument("prompt", help="Prompt to execute.") 23 | parser.add_argument("--max-rounds", type=int, default=10) 24 | parser.add_argument( 25 | "--model", 26 | default="text-davinci-003", 27 | help="OpenAI text completion model to use.", 28 | ) 29 | parser.add_argument( 30 | "--image", 31 | default="ubuntu:latest", 32 | help="Docker image to use." 33 | "The current hard-coded prompt works for Debian-derived distributions.", 34 | ) 35 | parser.add_argument( 36 | "--shell-command", 37 | default="/bin/bash", 38 | help="Shell to invoke within the container.", 39 | ) 40 | parser.add_argument( 41 | "--save-transcript", action="store_true", help="Save transcript to file" 42 | ) 43 | parser.add_argument( 44 | "--wipe", 45 | action="store_true", 46 | help="Start with a fresh container even if one exists for this directory.", 47 | ) 48 | parser.add_argument( 49 | "--rm", 50 | action="store_true", 51 | help="Remove the container after the task is complete.", 52 | ) 53 | args = parser.parse_args() 54 | 55 | if "OPENAI_API_KEY" not in environ: 56 | log.error( 57 | "OpenAI API key not found." 58 | "Please set the OPENAI_API_KEY environment variable." 59 | ) 60 | return 61 | 62 | llm = LLM(args.model, save_transcript=args.save_transcript) 63 | container = DockerContainer( 64 | args.image, args.shell_command, wipe=args.wipe, remove_on_exit=args.rm 65 | ) 66 | task_runner = TaskDriver(args.prompt, container, llm) 67 | 68 | try: 69 | for _ in range(args.max_rounds): 70 | result = task_runner.step() 71 | if result: 72 | break 73 | else: 74 | log.warning( 75 | "Task did not complete, stopped after reaching its round limit. " 76 | "Usually this means it got stuck, but you can try passing a higher " 77 | "value to --max-rounds.", 78 | max_rounds=args.max_rounds, 79 | ) 80 | except KeyboardInterrupt: 81 | log.warning("Task interrupted by user.") 82 | except EOFError: 83 | log.warning("Task interrupted by user.") 84 | 85 | 86 | if __name__ == "__main__": 87 | main() 88 | -------------------------------------------------------------------------------- /src/botsh/prompt.py: -------------------------------------------------------------------------------- 1 | from jinja2 import Environment, PackageLoader 2 | 3 | from botsh.history import CommandExecution 4 | from botsh.logging import log 5 | 6 | ENV = Environment(loader=PackageLoader("botsh", "templates")) 7 | TEMPLATE = ENV.get_template("prompt.jinja2") 8 | 9 | PROMPT_TRAILER = "EXPLANATION:" 10 | COMMAND_TRAILER = "COMMAND:" 11 | 12 | def generate_prompt(task: str, history: list[CommandExecution]) -> str: 13 | return ( 14 | TEMPLATE.render( 15 | task=task, 16 | history=history, 17 | ) 18 | + PROMPT_TRAILER 19 | ) 20 | 21 | 22 | class Response: 23 | def __init__(self, command, explanation): 24 | self.command = command 25 | self.explanation = explanation 26 | 27 | command = None 28 | explanation = None 29 | 30 | 31 | def parse_response(response: str) -> dict[str, str]: 32 | lines = iter(response.splitlines()) 33 | explanation = next(lines, "") 34 | 35 | command = "" 36 | command_line = next(lines, "") 37 | if command_line.startswith(COMMAND_TRAILER): 38 | command = command_line[len(COMMAND_TRAILER) + 1 :] 39 | else: 40 | log.warning( 41 | "Expected a command, got this instead.", command_line=command_line 42 | ) 43 | 44 | for line in lines: 45 | command += line.strip() 46 | 47 | return Response(command=command, explanation=explanation) 48 | -------------------------------------------------------------------------------- /src/botsh/task_driver.py: -------------------------------------------------------------------------------- 1 | from botsh.docker_exec import DockerContainer 2 | from botsh.history import CommandExecution 3 | from botsh.llm import LLM 4 | from botsh.logging import log 5 | from botsh.prompt import parse_response 6 | 7 | 8 | class TaskDriver: 9 | history: list[CommandExecution] 10 | task: str 11 | container: DockerContainer 12 | llm: LLM 13 | 14 | def __init__(self, task: str, container: DockerContainer, llm: LLM): 15 | self.task = task 16 | self.llm = llm 17 | self.container = container 18 | self.history = list() 19 | 20 | # Run a few commands. These serve both to orient the agent, and to 21 | # provide some examples of what we want. 22 | self.run_command_and_add_to_history( 23 | "I would like to know five files in the current directory.", 24 | "ls -al | head -n 5", 25 | True, 26 | ) 27 | 28 | self.run_command_and_add_to_history( 29 | "I would like to know which directory I am in.", "pwd", True 30 | ) 31 | 32 | def run_command_and_add_to_history( 33 | self, explanation: str, command: str, quiet: bool = False 34 | ): 35 | exit_code, output = self.container.run_command(command, quiet) 36 | self.history.append(CommandExecution(explanation, command, output, exit_code)) 37 | return output 38 | 39 | def step(self): 40 | response = self.llm.completion(self.task, self.history) 41 | 42 | result = parse_response(response) 43 | 44 | if result.command == "": 45 | log.info( 46 | "Agent indicated that the task is complete.", 47 | explanation=result.explanation, 48 | ) 49 | return True 50 | else: 51 | log.info( 52 | "Agent requested a command.", 53 | command=result.command, 54 | explanation=result.explanation, 55 | ) 56 | 57 | exit_code, output = self.container.run_command(result.command) 58 | lines = output.splitlines() 59 | if len(lines) > 10: 60 | truncated = len(lines) - 10 61 | output = ( 62 | f"[ {truncated} lines truncated ]" 63 | + "\n".join(lines[-10:]) 64 | + "\n[...]\n" 65 | ) 66 | 67 | self.history.append( 68 | CommandExecution(result.explanation, result.command, output, exit_code) 69 | ) 70 | -------------------------------------------------------------------------------- /src/botsh/templates/prompt.jinja2: -------------------------------------------------------------------------------- 1 | You are given a task to accomplish by running commands in a Bash shell on Ubuntu Linux. 2 | 3 | If you get a "command not found" error, you should install that software using `apt-get -qq`. Do not run software that requires user interaction. 4 | 5 | Any files referenced in the prompt are relative to the current directory. If you need to write a temporary file, write it to /tmp. 6 | 7 | Answer in the format: 8 | 9 | EXPLANATION: explain why you are running the command 10 | COMMAND: the command you are running 11 | 12 | When you have completed the task or get stuck, respond with an empty COMMAND. 13 | 14 | If you think the task is impossible, explain your reasoning in the EXPLANATION and return an empty COMMAND. If a command fails, DO NOT repeat it. 15 | 16 | The task you are trying to accomplish is: 17 | 18 | {{task}} 19 | 20 | {% for exec in history %} 21 | EXPLANATION: {{exec.explanation}} 22 | COMMAND: {{exec.command}} 23 | OUTPUT: {% if exec.result|length > 1024 %}[{{exec.result|length - 1024}} characters snipped] 24 | {{exec.result[-1024:]}}{% else %}{{exec.result}}{% endif %} 25 | EXIT_CODE: {{exec.exit_code}} {% if exec.exit_code == 0 %}OK{% else %}FAIL{% endif %} 26 | {% endfor %} 27 | --------------------------------------------------------------------------------