├── .devcontainer └── devcontainer.json ├── .env.example ├── .gitignore ├── .sesskey ├── LICENSE ├── README.md ├── assets ├── fonts │ ├── geist-mono │ │ ├── GeistMonoVF.woff │ │ └── GeistMonoVF.woff2 │ └── geist │ │ ├── Geist-Medium.woff │ │ ├── Geist-Medium.woff2 │ │ ├── GeistVF.woff │ │ └── GeistVF.woff2 ├── footer-shapes.svg ├── hero-shapes.svg ├── icons │ ├── accordion-minus-icon.svg │ ├── accordion-plus-icon.svg │ ├── arrow-left.svg │ ├── arrow-right.svg │ ├── arrow-up-right-white.svg │ ├── arrow-up-right.svg │ ├── copy-icon.svg │ ├── dot.svg │ ├── github-mark-white.svg │ ├── github-mark.svg │ ├── loading-spinner.svg │ ├── magic-wand.svg │ ├── minus-icon.svg │ ├── plus-icon.svg │ ├── regenerate.svg │ ├── youtube-icon.svg │ └── youtube.svg ├── logo.svg ├── og-image.png ├── og-sq.png └── testimonials │ ├── daniel-roy-greenfield.png │ ├── daniel-roy.png │ ├── giles-thomas.png │ ├── guillermo-rauch.jpg │ └── jake-cooper.png ├── content.py ├── css ├── input.css ├── main.css └── tailwind.css ├── data └── devcontainers.db ├── dependency_tree.txt ├── favicon-dark.ico ├── favicon.ico ├── helpers ├── __init__.py ├── devcontainer_helpers.py ├── github_helpers.py ├── jinja_helper.py ├── openai_helpers.py └── token_helpers.py ├── js └── main.js ├── main.py ├── migrate.py ├── models.py ├── prompts └── devcontainer.jinja ├── requirements.txt ├── schemas.py ├── schemas └── devContainer.base.schema.json ├── supabase_client.py ├── tailwind.config.js └── test.py /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/python 3 | { 4 | "name": "FastHTML", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/python:3.12-bookworm", 7 | 8 | // Features to add to the dev container. More info: https://containers.dev/features. 9 | // "features": {}, 10 | 11 | // Configure tool-specific properties. 12 | "customizations": { 13 | // Configure properties specific to VS Code. 14 | "vscode": { 15 | // Set *default* container specific settings.json values on container create. 16 | "settings": { 17 | "python.defaultInterpreterPath": "/usr/bin/python" 18 | }, 19 | 20 | // Add the IDs of extensions you want installed when the container is created. 21 | "extensions": [ 22 | "streetsidesoftware.code-spell-checker", 23 | "qwtel.sqlite-viewer", 24 | "GitHub.copilot", 25 | "GitHub.copilot-chat", 26 | "ms-python.python", 27 | "ms-python.vscode-pylance", 28 | "ms-python.isort", 29 | "njpwerner.autodocstring", 30 | "ms-python.pylint", // Pylint extension 31 | "ms-python.flake8", // Flake8 extension 32 | "ms-python.black-formatter", // Black formatter extension 33 | "ms-python.autopep8" // autopep8 extension 34 | ] 35 | } 36 | }, 37 | 38 | // Use 'portsAttributes' to set default properties for specific forwarded ports. 39 | // More info: https://containers.dev/implementors/json_reference/#port-attributes 40 | "portsAttributes": { 41 | "5001": { 42 | "label": "FastHTML", 43 | "onAutoForward": "openBrowser" 44 | } 45 | }, 46 | 47 | // https://containers.dev/implementors/json_reference/#lifecycle-scripts 48 | "postCreateCommand": "pip3 install -r requirements.txt || true && curl -fsSL cli.new | bash -s -- -y" 49 | // "postAttachCommand": "python main.py" 50 | } -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | AZURE_OPENAI_API_KEY=YOURKEY 2 | AZURE_OPENAI_ENDPOINT=https://YOURENDPOINT.openai.azure.com 3 | AZURE_OPENAI_API_VERSION=2024-02-01 4 | MODEL=gpt-4o-mini 5 | EMBEDDING=text-embedding-3-small 6 | EMBEDDING_MODEL_MAX_TOKENS=8192 7 | GITHUB_TOKEN=YOURTOKEN 8 | SUPABASE_URL=https://YOURLINK.supabase.co 9 | SUPABASE_KEY=your_supabase_key 10 | SUPABASE_DB_URL=your_supabase_db_url # use transaction pooler, example: postgresql://postgres.pnewjmyiuazlkssdmhsc:[YOUR-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:6543/postgres?sslmode=require -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Database 2 | *.db 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # poetry 101 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 102 | # This is especially recommended for binary packages to ensure reproducibility, and is more 103 | # commonly ignored for libraries. 104 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 105 | #poetry.lock 106 | 107 | # pdm 108 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 109 | #pdm.lock 110 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 111 | # in version control. 112 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 113 | .pdm.toml 114 | .pdm-python 115 | .pdm-build/ 116 | 117 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 118 | __pypackages__/ 119 | 120 | # Celery stuff 121 | celerybeat-schedule 122 | celerybeat.pid 123 | 124 | # SageMath parsed files 125 | *.sage.py 126 | 127 | # Environments 128 | .env 129 | .venv 130 | env/ 131 | venv/ 132 | ENV/ 133 | env.bak/ 134 | venv.bak/ 135 | 136 | # Spyder project settings 137 | .spyderproject 138 | .spyproject 139 | 140 | # Rope project settings 141 | .ropeproject 142 | 143 | # mkdocs documentation 144 | /site 145 | 146 | # mypy 147 | .mypy_cache/ 148 | .dmypy.json 149 | dmypy.json 150 | 151 | # Pyre type checker 152 | .pyre/ 153 | 154 | # pytype static type analyzer 155 | .pytype/ 156 | 157 | # Cython debug symbols 158 | cython_debug/ 159 | 160 | # PyCharm 161 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 162 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 163 | # and can be added to the global gitignore or merged into this file. For a more nuclear 164 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 165 | #.idea/ 166 | 167 | # OS generated files # 168 | ###################### 169 | .DS_Store 170 | .DS_Store? 171 | ._* 172 | .Spotlight-V100 173 | .Trashes 174 | ehthumbs.db 175 | Thumbs.db -------------------------------------------------------------------------------- /.sesskey: -------------------------------------------------------------------------------- 1 | 48023861-a013-4232-a173-dae96c7ec665 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2024 Daytona Platforms, Inc. 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Open in Codeanywhere](https://codeanywhere.com/img/open-in-codeanywhere-btn.svg)](https://app.codeanywhere.com/#https://github.com/daytonaio/devcontainer-generator) 2 | 3 | # DevContainer Generator 4 | 5 | Welcome to the **devcontainer-generator** project! This tool helps you automatically generate `devcontainer.json` files for your development environments based on the structure and contents of a given GitHub repository. 6 | 7 | ## Table of Contents 8 | 9 | 1. [Project Structure](#project-structure) 10 | 2. [Installation](#installation) 11 | 3. [Configuration](#configuration) 12 | 4. [Usage](#usage) 13 | 5. [Setting Up Daytona Workspace](#setting-up-daytona-workspace) 14 | 6. [Contributing](#contributing) 15 | 7. [License](#license) 16 | 17 | ## Project Structure 18 | 19 | ``` 20 | devcontainer-generator 21 | ├── tests.ipynb 22 | ├── requirements.txt 23 | ├── schemas 24 | │ └── devContainer.base.schema.json 25 | ├── README.md 26 | ├── main.py 27 | └── data 28 | └── devcontainers.db 29 | ``` 30 | 31 | - **tests.ipynb**: Jupyter Notebook containing tests and exploratory data analysis. 32 | - **requirements.txt**: List of dependencies needed to run the project. 33 | - **schemas/**: Directory containing the JSON schema for the `devcontainer.json` file. 34 | - **README.md**: This documentation file. 35 | - **main.py**: Main script to generate `devcontainer.json` files. 36 | - **data/**: Directory containing the SQLite database files. 37 | 38 | ## Installation 39 | 40 | To run this project in Daytona, you'll need to have Daytona installed. Follow these steps to set up the project: 41 | 42 | 1. **Install Daytona**: 43 | ```bash 44 | (curl -L https://download.daytona.io/daytona/install.sh | sudo bash) && daytona server stop && daytona server -y && daytona 45 | ``` 46 | 47 | 2. **Create new project and run IDE**: 48 | ```bash 49 | daytona create https://github.com/daytonaio/devcontainer-generator.git 50 | ``` 51 | 52 | 3. **Set up environment variables**: 53 | Create a `.env` file in the project's root directory and add the following environment variables: 54 | ```dotenv 55 | AZURE_OPENAI_ENDPOINT=your_azure_openai_endpoint 56 | AZURE_OPENAI_API_KEY=your_azure_openai_api_key 57 | AZURE_OPENAI_API_VERSION=your_azure_openai_api_version 58 | MODEL=your_model_name 59 | GITHUB_TOKEN=your_github_token 60 | SUPABASE_URL=your_supabase_url 61 | SUPABASE_KEY=your_supabase_api_key 62 | SUPABASE_DB_URL=your_supabase_db_url 63 | ``` 64 | 65 | ## Configuration 66 | 67 | ### Azure OpenAI and Instructor Clients 68 | 69 | Ensure the following environment variables are set in your `.env` file: 70 | 71 | ```dotenv 72 | AZURE_OPENAI_ENDPOINT=your_azure_openai_endpoint 73 | AZURE_OPENAI_API_KEY=your_azure_openai_api_key 74 | AZURE_OPENAI_API_VERSION=your_azure_openai_api_version 75 | MODEL=your_model_name 76 | GITHUB_TOKEN=your_github_token 77 | 78 | ``` 79 | 80 | ### Supabase Database Setup 81 | 82 | Run the `migrate.py` script to create the `devcontainers` table in Supabase. Here's how you can do it: 83 | 84 | ```bash 85 | python migrate.py 86 | ``` 87 | 88 | After creating the table, you can use the Supabase client in your Python code to interact with the database. 89 | 90 | ### JSON Schema 91 | 92 | The JSON schema for the `devcontainer.json` file is located in `schemas/devContainer.base.schema.json`. 93 | 94 | ## Usage 95 | 96 | Run the `main.py` script to start the FastHTML app and generate `devcontainer.json` files. Here's how you can do it: 97 | 98 | ```bash 99 | python main.py 100 | ``` 101 | 102 | **Instructions:** 103 | 104 | 1. After starting the app, open your web browser and go to `http://localhost:8000`. 105 | 2. Enter the URL of the GitHub repository for which you want to generate a `devcontainer.json` file. 106 | 3. Click the "Generate devcontainer.json" button. 107 | 4. The generated `devcontainer.json` will be displayed and can be copied to your clipboard. 108 | 109 | ## Setting Up Daytona Workspace 110 | 111 | **Steps to Set Up Daytona Workspace** 112 | 113 | 1. Create [Daytona](https://github.com/daytonaio/daytona) Workspace: 114 | 115 | ```bash 116 | daytona create https://github.com/nkkko/devcontainer-generator 117 | ``` 118 | 119 | 2. Select Preferred IDE: 120 | 121 | ```bash 122 | daytona ide 123 | ``` 124 | 125 | 3. Open the Workspace: 126 | 127 | ```bash 128 | daytona code 129 | ``` 130 | 131 | ## Contributing 132 | 133 | We welcome contributions! Please read our [CONTRIBUTING.md](CONTRIBUTING.md) file for guidelines on how to contribute to this project. 134 | 135 | ## License 136 | 137 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details. 138 | 139 | --- 140 | 141 | Thank you for using **devcontainer-generator**! If you have any questions or issues, feel free to open an issue on GitHub. 142 | -------------------------------------------------------------------------------- /assets/fonts/geist-mono/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/devcontainer-generator/0e00603b4163c0dd97e2ae450e5bf44576338ecd/assets/fonts/geist-mono/GeistMonoVF.woff -------------------------------------------------------------------------------- /assets/fonts/geist-mono/GeistMonoVF.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/devcontainer-generator/0e00603b4163c0dd97e2ae450e5bf44576338ecd/assets/fonts/geist-mono/GeistMonoVF.woff2 -------------------------------------------------------------------------------- /assets/fonts/geist/Geist-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/devcontainer-generator/0e00603b4163c0dd97e2ae450e5bf44576338ecd/assets/fonts/geist/Geist-Medium.woff -------------------------------------------------------------------------------- /assets/fonts/geist/Geist-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/devcontainer-generator/0e00603b4163c0dd97e2ae450e5bf44576338ecd/assets/fonts/geist/Geist-Medium.woff2 -------------------------------------------------------------------------------- /assets/fonts/geist/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/devcontainer-generator/0e00603b4163c0dd97e2ae450e5bf44576338ecd/assets/fonts/geist/GeistVF.woff -------------------------------------------------------------------------------- /assets/fonts/geist/GeistVF.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/devcontainer-generator/0e00603b4163c0dd97e2ae450e5bf44576338ecd/assets/fonts/geist/GeistVF.woff2 -------------------------------------------------------------------------------- /assets/footer-shapes.svg: -------------------------------------------------------------------------------- 1 | 9 | 17 | 26 | 35 | 36 | 40 | 44 | 48 | 49 | 50 | 54 | 58 | 62 | 63 | 72 | 81 | 90 | 99 | 108 | 117 | 118 | -------------------------------------------------------------------------------- /assets/hero-shapes.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 18 | 27 | 36 | 37 | 41 | 45 | 49 | 50 | 51 | 55 | 59 | 63 | 64 | 73 | 82 | 91 | 100 | 109 | 118 | 119 | -------------------------------------------------------------------------------- /assets/icons/accordion-minus-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/accordion-plus-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/arrow-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/arrow-up-right-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/icons/arrow-up-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/icons/copy-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/icons/dot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/icons/github-mark-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/github-mark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/loading-spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | -------------------------------------------------------------------------------- /assets/icons/magic-wand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /assets/icons/minus-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/plus-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/regenerate.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/youtube-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /assets/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/devcontainer-generator/0e00603b4163c0dd97e2ae450e5bf44576338ecd/assets/og-image.png -------------------------------------------------------------------------------- /assets/og-sq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/devcontainer-generator/0e00603b4163c0dd97e2ae450e5bf44576338ecd/assets/og-sq.png -------------------------------------------------------------------------------- /assets/testimonials/daniel-roy-greenfield.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/devcontainer-generator/0e00603b4163c0dd97e2ae450e5bf44576338ecd/assets/testimonials/daniel-roy-greenfield.png -------------------------------------------------------------------------------- /assets/testimonials/daniel-roy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/devcontainer-generator/0e00603b4163c0dd97e2ae450e5bf44576338ecd/assets/testimonials/daniel-roy.png -------------------------------------------------------------------------------- /assets/testimonials/giles-thomas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/devcontainer-generator/0e00603b4163c0dd97e2ae450e5bf44576338ecd/assets/testimonials/giles-thomas.png -------------------------------------------------------------------------------- /assets/testimonials/guillermo-rauch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/devcontainer-generator/0e00603b4163c0dd97e2ae450e5bf44576338ecd/assets/testimonials/guillermo-rauch.jpg -------------------------------------------------------------------------------- /assets/testimonials/jake-cooper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/devcontainer-generator/0e00603b4163c0dd97e2ae450e5bf44576338ecd/assets/testimonials/jake-cooper.png -------------------------------------------------------------------------------- /content.py: -------------------------------------------------------------------------------- 1 | from fasthtml.common import * 2 | 3 | description = 'Generate Custom Dev Containers in Seconds with AI' 4 | _blank = dict(target="_blank", rel="noopener noreferrer") 5 | 6 | def caution_section(): 7 | return Div( 8 | H3( 9 | "⚠️ Caution: AI Generated Code", 10 | cls="text-2xl font-bold text-center mb-4", 11 | style="padding-left: 20px; padding-top: 10px; padding-bottom: 10px;" 12 | ), 13 | Ul( 14 | Li("Make sure to review the generated devcontainer.json file before running it in your development environment."), 15 | Li("The best way to run AI generated code is inside sandboxed dev environments like those managed by Daytona."), 16 | cls="list-disc list-inside text-lg text-center mt-4" 17 | ), 18 | cls="container mx-auto px-4 py-16 bg-yellow-100", 19 | style="background-color: #ffff66; padding-bottom: 5px; border-radius: 5px; margin-top: 10px; margin-bottom: 10px;" 20 | ) 21 | 22 | def hero_section(): 23 | return Section( 24 | Div( 25 | H1("AI-Powered Dev Environment Setup", cls="text-3xl font-bold text-center"), 26 | H2("From GitHub to Ready-to-Code in Seconds"), 27 | P("Paste your GitHub URL and get a custom devcontainer.json to simplify your development.", cls="text-lg text-center mt-4"), 28 | cls="container mx-auto px-4 py-16" 29 | ), 30 | cls="bg-gray-100", 31 | ) 32 | 33 | 34 | def generator_section(): 35 | return Section( 36 | Form( 37 | Group( 38 | Input(type="text", name="repo_url", placeholder="Paste your Github repo URL, or select a repo to get started", cls="form-input", list="repo-list"), 39 | Datalist( 40 | Option(value="https://github.com/devcontainers/templates"), 41 | Option(value="https://github.com/JetBrains/devcontainers-examples"), 42 | Option(value="https://github.com/devcontainers/cli"), 43 | id="repo-list" 44 | ), 45 | Button( 46 | Div( 47 | Img(src="assets/icons/magic-wand.svg", cls="svg-icon"), 48 | Img(src="assets/icons/loading-spinner.svg", cls="htmx-indicator"), 49 | cls="icon-container" 50 | ), 51 | Span("Generate", cls="button-text"), 52 | cls="button", 53 | id="generate-button", 54 | hx_post="/generate", 55 | hx_target="#result", 56 | hx_indicator="#generate-button" 57 | ) 58 | ), 59 | Div(id="url-error", cls="error-message"), 60 | id="generate-form", 61 | cls="form", 62 | hx_post="/generate", 63 | hx_target="#result", 64 | hx_indicator=".htmx-indicator" 65 | ), 66 | Div(id="result"), 67 | cls="container" 68 | ) 69 | 70 | def benefits_section(): 71 | return Section( 72 | Div( 73 | H2("Benefits of Development Containers", cls="text-2xl font-bold text-center mb-8"), 74 | Div( 75 | benefit_card("Consistency", "Ensure every team member uses the same development environment, regardless of their local setup."), 76 | benefit_card("Reproducibility", "Easily recreate a specific development environment for bug reproduction or testing."), 77 | benefit_card("Isolation", "Keep project dependencies contained, preventing conflicts with other projects."), 78 | benefit_card("Onboarding", "Simplify onboarding for new developers by providing a ready-to-go environment."), 79 | cls="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 container mx-auto", 80 | ), 81 | cls="container mx-auto px-4 py-16", 82 | ), 83 | cls="bg-gray-100" 84 | ) 85 | 86 | def benefit_card(title, description): 87 | return Div( 88 | H3(title, cls="text-xl font-bold mb-2"), 89 | P(description, cls="text-gray-700"), 90 | cls="bg-white rounded-md shadow-md p-6" 91 | ) 92 | 93 | def setup_section(): 94 | return Section( 95 | Div( 96 | H2("Setting Up Dev Containers in Your Repository", cls="text-2xl font-bold text-center mb-8"), 97 | P("Follow these steps:", cls="text-gray-700 container mx-auto mb-4"), 98 | Ul( 99 | Li("Create a `.devcontainer/devcontainer.json` file in the root of your repository."), 100 | Li("Open your repository in VS Code or ", 101 | A("Daytona", href="https://daytona.io", **_blank, 102 | cls="border-b-2 border-b-black/30 hover:border-b-black/80"), 103 | "and it will automatically run your dev container."), 104 | cls="list-decimal list-inside text-gray-700 mb-8" 105 | ), 106 | P("Now you're ready to develop inside your consistent and isolated dev environment!", cls="text-gray-700 container mx-auto"), 107 | cls="container mx-auto px-4 py-16" 108 | ), 109 | ) 110 | 111 | def manifesto(): 112 | return Section( 113 | Div( 114 | H2("The Open Run Manifesto", cls="text-3xl font-bold text-center mb-8"), 115 | P("Imagine a world where any software, any code, just runs. No more complex setups. No more compatibility issues. Just pure creation.", cls="text-gray-700 container mx-auto mb-4"), 116 | P("Key points:", cls="text-gray-700 container mx-auto mb-4"), 117 | Ul( 118 | Li("Developers waste up to 56% of their time on environment setup and maintenance."), 119 | Li("Code contributions are often hindered by complicated development environments."), 120 | Li("We envision a future where AI handles complex setup tasks."), 121 | Li("Our goal: Make any code instantly runnable by anyone, anywhere."), 122 | cls="list-disc list-inside text-gray-700 mb-8" 123 | ), 124 | P("Join us in making instant-run software a reality for all developers.", cls="text-gray-700 container mx-auto mb-8"), 125 | A("Read the full manifesto", 126 | href="/manifesto", 127 | cls="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded", 128 | **_blank 129 | ), 130 | cls="container mx-auto px-4 py-16" 131 | ), 132 | ) 133 | 134 | def examples_section(): 135 | return Section( 136 | Div( 137 | H2("Examples from Popular Repositories", cls="text-2xl font-bold text-center mb-8"), 138 | P("Explore how popular projects utilize dev containers for efficient development:", cls="text-gray-700 text-center mb-8 container mx-auto"), 139 | Div( 140 | example_card("Microsoft/vscode", "https://github.com/microsoft/vscode/tree/main/.devcontainer"), 141 | example_card("daytonaio/daytona", "https://github.com/daytonaio/daytona/blob/main/.devcontainer"), 142 | example_card("withastro/astro", "https://github.com/withastro/astro/tree/main/.devcontainer"), 143 | cls="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 container mx-auto", 144 | ), 145 | cls="container mx-auto px-4 py-16" 146 | ), 147 | cls="bg-gray-100" 148 | ) 149 | 150 | def example_card(repo_name, url): 151 | return Div( 152 | A(repo_name, href=url, target="_blank", cls="text-lg font-bold hover:underline"), 153 | cls="bg-white rounded-md shadow-md p-6" 154 | ) 155 | 156 | def faq_section(): 157 | return Section( 158 | Div( 159 | H2("Frequently Asked Questions", cls="text-2xl font-bold text-center mb-8"), 160 | Div( 161 | faq_item("What are the advantages of using a dev container?", 162 | "Dev containers provide a consistent and isolated environment for development, ensuring that all team members have the same setup and dependencies."), 163 | faq_item("How does the dev container generation process work?", 164 | "The process is simple: 1) Enter your GitHub repo URL, 2) Our AI analyzes your project, 3) Get a custom devcontainer.json in seconds, 4) Start coding in your optimized environment."), 165 | faq_item("Which development tools are compatible with the generated dev containers?", 166 | "Our generated dev containers work seamlessly with Daytona, VS Code, GitHub Codespaces, and other popular dev tools."), 167 | faq_item("What are the advantages of using a dev container?", 168 | "Dev containers provide a consistent and isolated environment for development, ensuring that all team members have the same setup and dependencies."), 169 | faq_item("Can I customize the generated dev container?", 170 | "Yes, you can further customize the dev container for example by adding a `Dockerfile` to the `.devcontainer` directory or adding more Dev Container features."), 171 | faq_item("What if my repository already has a dev container?", 172 | "Our generator will detect existing `devcontainer.json` files and try to use them as a starting point for customization. You can also choose to regenerate a new configuration."), 173 | faq_item("How often should I update my dev container configuration?", 174 | "It's a good practice to update your dev container configuration whenever you make significant changes to your project's dependencies or development environment. You can easily regenerate the configuration using our tool."), 175 | faq_item("Does this service work with private GitHub repositories?", 176 | "Currently, our dev container generator only works with public GitHub repositories. This ensures that we can analyze the necessary metadata without requiring additional authentication or permissions."), 177 | cls="accordion container mx-auto", 178 | ), 179 | cls="container mx-auto px-4 py-16", 180 | ), 181 | ) 182 | 183 | def faq_item(question, answer): 184 | return Div( 185 | H3(question, cls="text-lg font-medium cursor-pointer"), 186 | Div(P(answer, cls="text-gray-700 mt-2"), cls="hidden"), 187 | cls="mb-4", 188 | **{"_surreal": "on click toggle class:hidden for next-sibling"} 189 | ) 190 | 191 | def cta_section(): 192 | return Section( 193 | Div( 194 | H2("Ready to Simplify Your Development Environment?", cls="text-2xl font-bold text-center mb-4"), 195 | P("Experience the power of Daytona's flexible and easy development environment platform.", cls="text-lg text-center mb-6"), 196 | A( 197 | Span( 198 | Img(src="assets/icons/github-mark-white.svg", cls="github-icon"), 199 | "Get Daytona Now", 200 | cls="button-content" 201 | ), 202 | role="button", 203 | href="https://github.com/daytonaio/daytona", 204 | target="_blank", 205 | title="Get Daytona", 206 | cls="cta-button" 207 | ), 208 | cls="container mx-auto px-4 py-16 text-center" 209 | ), 210 | cls="bg-gray-100" 211 | ) 212 | 213 | def footer_section(): 214 | return Footer( 215 | Div( 216 | P("© 2024 ", 217 | A("Daytona Platforms Inc.", href="https://daytona.io", **_blank, 218 | cls="border-b-2 border-b-black/30 hover:border-b-black/80"), 219 | "All rights reserved.", cls="text-center text-gray-600"), 220 | cls="container mx-auto px-4 py-8" 221 | ), 222 | cls="bg-gray-100", 223 | ) 224 | 225 | def manifesto_page(): 226 | return ( 227 | Title("The Open Run Manifesto: Liberating Software for All"), 228 | Main( 229 | Section( 230 | Div( 231 | H1("The Open Run Manifesto", cls="text-4xl font-bold text-center mb-8 text-blue-600"), 232 | P("Liberating Software for All", cls="text-2xl text-gray-700 text-center mb-12"), 233 | Div( 234 | P("Imagine a world where any software, any code, just runs. No more complex setups. No more compatibility issues. Just pure creation.", cls="mb-4"), 235 | P("We're entering an age where coding's power is accessible to all, not just developers.", cls="mb-4"), 236 | P("For too long, we've accepted artificial barriers between code and execution. Developers waste nearly half of their productive time—up to 56%—just setting up and maintaining their development environments. This isn't just inefficiency; it's a creativity killer.", cls="mb-4"), 237 | P("We've seen countless open source contributions and collaborative efforts stifled by complicated development environments, intricate setup processes, and the all-too-familiar \"It works on my machine\" predicament.", cls="mb-4"), 238 | P("This isn't just lost time; it's lost creation.", cls="mb-4"), 239 | P("No more.", cls="font-bold text-blue-600 mb-4"), 240 | P("Imagine if every developer could instantly dive into coding, regardless of the project or technology stack.", cls="mb-4"), 241 | P("Let's build a future where AI handles the complex task of setting up and configuring software.", cls="mb-8"), 242 | cls="text-lg text-gray-700 mb-12" 243 | ), 244 | H3("Imagine:", cls="text-3xl font-bold mt-12 mb-6 text-blue-600"), 245 | Ul( 246 | Li("Checking out a repo and having it run instantly, without any manual intervention"), 247 | Li("No need to read lengthy readme files or follow complex configuration steps"), 248 | Li("A world where the focus is on creation, not on fighting with setup processes"), 249 | cls="list-disc list-inside text-lg text-gray-700 mb-12 pl-8 space-y-2" 250 | ), 251 | P("Today, infrastructure-as-code configuration files offer a solution, but widespread adoption remains limited. Even among those who use them, maintenance can be inconsistent. The goal is to automate these configuration files, which simplify environment setup but are often absent or overly complex. We should make them standard for all projects globally, allowing anyone to run any code instantly.", cls="text-lg text-gray-700 mb-8"), 252 | P("We are taking a first step: automating dev container creation with devcontainer.ai. This open-source initiative paves the way for a future where all code is instantly accessible and runnable by anyone, anywhere.", cls="text-lg text-gray-700 mb-8"), 253 | P("We need your help to achieve this goal. Join us in making instant-run software a reality for all developers.", cls="text-xl text-gray-700 mb-8 font-bold"), 254 | P("Let's build a world where code isn't just open source, but free to be used by anyone with an idea.", cls="text-lg text-gray-700 mb-12"), 255 | A( 256 | Span( 257 | Img(src="assets/icons/github-mark-white.svg", cls="github-icon"), 258 | "Contribute to devcontainer.ai", 259 | cls="button-content" 260 | ), 261 | role="button", 262 | href="https://github.com/daytonaio/devcontainer-generator", 263 | target="_blank", 264 | title="Get Daytona", 265 | cls="cta-button" 266 | ), 267 | Div( 268 | A("Back to Home", href="/", cls="bg-blue-500 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg text-lg"), 269 | cls="mt-16 text-center" 270 | ), 271 | cls="container mx-auto px-8 py-16 max-w-4xl" 272 | ), 273 | cls="bg-gray-50" 274 | ), 275 | footer_section() 276 | ) 277 | ) -------------------------------------------------------------------------------- /css/input.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --pico-font-size: 100%; 3 | } 4 | 5 | @font-face { 6 | font-family: "Geist"; 7 | src: url("/assets/fonts/geist/GeistVF.woff2") format("woff2"), 8 | url("/assets/fonts/geist/GeistVF.woff") format("woff"); 9 | font-weight: normal; 10 | font-style: normal; 11 | font-display: swap; 12 | } 13 | 14 | @font-face { 15 | font-family: "Geist"; 16 | src: url("/assets/fonts/geist/Geist-Medium.woff2") format("woff2"), 17 | url("/assets/fonts/geist/Geist-Medium.woff") format("woff"); 18 | font-weight: 500; 19 | font-style: normal; 20 | font-display: swap; 21 | } 22 | 23 | @font-face { 24 | font-family: "Geist Mono"; 25 | src: url("/assets/fonts/geist-mono/GeistMonoVF.woff2") format("woff2"), 26 | url("/assets/fonts/geist-mono/GeistMonoVF.woff") format("woff"); 27 | font-weight: normal; 28 | font-style: normal; 29 | font-display: swap; 30 | } 31 | 32 | body { 33 | padding: 20px; 34 | font-family: Geist, sans-serif; 35 | } 36 | 37 | .container { 38 | max-width: 800px; 39 | margin: 0 auto; 40 | } 41 | 42 | pre { 43 | background-color: #f4f4f4; 44 | padding: 15px; 45 | border-radius: 5px; 46 | position: relative; 47 | } 48 | 49 | .code-container { 50 | position: relative; 51 | overflow: visible; 52 | } 53 | 54 | .button-group { 55 | position: absolute; 56 | top: 0.5rem; 57 | right: 0.5rem; 58 | display: flex; 59 | gap: 0.25rem; 60 | } 61 | 62 | .icon-button { 63 | background-color: rgba(73, 73, 73, 0.5); 64 | border: none; 65 | border-radius: 0.25rem; 66 | padding: 0.25rem; 67 | cursor: pointer; 68 | transition: background-color 0.3s ease; 69 | width: 1.75rem; 70 | height: 1.75rem; 71 | display: flex; 72 | align-items: center; 73 | justify-content: center; 74 | } 75 | 76 | .icon-button:hover { 77 | background-color: rgba(73, 73, 73, 0.25); 78 | } 79 | 80 | .action-text { 81 | position: absolute; 82 | height: 1.75rem; 83 | top: 0rem; 84 | right: 3.75rem; 85 | background-color: #2196F3; 86 | color: rgb(50, 50, 50); 87 | padding: 0.5rem 0.25rem; 88 | border-radius: 0.25rem; 89 | font-size: 0.75rem; 90 | opacity: 0; 91 | transition: opacity 0.3s ease-in-out; 92 | } 93 | 94 | #regenerating-indicator { 95 | display: none; 96 | position: absolute; 97 | top: 0.5rem; 98 | right: 3.75rem; 99 | background-color: #2196F3; 100 | color: rgb(69, 69, 69); 101 | padding: 0.125rem 0.25rem; 102 | border-radius: 0.25rem; 103 | font-size: 0.75rem; 104 | } 105 | 106 | .htmx-request .regenerate-button { 107 | opacity: 0.5; 108 | cursor: not-allowed; 109 | } 110 | 111 | .htmx-request #regenerating-indicator { 112 | display: flex; 113 | } 114 | 115 | .error-message { 116 | color: red; 117 | margin-top: 5px; 118 | font-size: 0.9em; 119 | } 120 | 121 | .button { 122 | display: inline-flex; 123 | align-items: center; 124 | justify-content: center; 125 | } 126 | 127 | .icon-container { 128 | position: relative; 129 | width: 24px; 130 | height: 24px; 131 | margin-right: 8px; 132 | } 133 | 134 | .svg-icon, 135 | .htmx-indicator { 136 | position: absolute; 137 | top: 0; 138 | left: 0; 139 | width: 100%; 140 | height: 100%; 141 | transition: opacity 0.3s ease-in-out; 142 | } 143 | 144 | .htmx-indicator { 145 | opacity: 0; 146 | } 147 | 148 | .button.htmx-request .svg-icon { 149 | opacity: 0; 150 | } 151 | 152 | .button.htmx-request .htmx-indicator { 153 | opacity: 1; 154 | } 155 | 156 | /* Debug styles */ 157 | .debug-indicator { 158 | position: fixed; 159 | top: 10px; 160 | right: 10px; 161 | padding: 5px; 162 | background-color: #ff0; 163 | color: #000; 164 | font-weight: bold; 165 | z-index: 9999; 166 | } 167 | 168 | /* Typography */ 169 | 170 | .font-details-off { 171 | font-feature-settings: "clig" off, "liga" off; 172 | } 173 | 174 | H1 { 175 | font-family: Geist; 176 | font-size: 2.5rem; 177 | font-style: normal; 178 | font-weight: 500; 179 | line-height: 64px; 180 | /* 114.286% */ 181 | letter-spacing: -0.84px; 182 | } 183 | 184 | H2 { 185 | font-size: 2.5rem; 186 | font-style: normal; 187 | font-weight: 500; 188 | line-height: 3rem; 189 | /* 120% */ 190 | letter-spacing: -0.0375rem; 191 | } 192 | 193 | .xs-mono-body { 194 | font-family: "Geist Mono", monospace; 195 | font-size: 0.875rem; 196 | font-style: normal; 197 | font-weight: 500; 198 | line-height: 180%; 199 | /* 1.575rem */ 200 | } 201 | 202 | .mono-body { 203 | font-family: "Geist Mono", monospace; 204 | font-size: 1rem; 205 | font-style: normal; 206 | font-weight: 500; 207 | line-height: 180%; 208 | /* 1.8rem */ 209 | } 210 | 211 | .mono-s { 212 | font-family: "Geist Mono", monospace; 213 | font-size: 16px; 214 | font-style: normal; 215 | font-weight: 500; 216 | line-height: 180%; 217 | /* 28.8px */ 218 | } 219 | 220 | .xs-mono-body { 221 | font-family: "Geist Mono"; 222 | font-size: 0.875rem; 223 | font-style: normal; 224 | font-weight: 500; 225 | line-height: 180%; 226 | /* 1.575rem */ 227 | } 228 | 229 | .regular-body, 230 | .s-body { 231 | font-size: 1rem; 232 | font-style: normal; 233 | font-weight: 400; 234 | line-height: 1.5rem; 235 | /* 150% */ 236 | letter-spacing: -0.005rem; 237 | } 238 | 239 | .m-body { 240 | font-family: "Geist", sans-serif; 241 | font-size: 1.25rem; 242 | font-style: normal; 243 | font-weight: 400; 244 | line-height: 1.75rem; 245 | /* 140% */ 246 | letter-spacing: -0.0125rem; 247 | } 248 | 249 | .l-body { 250 | font-size: 1.5rem; 251 | font-style: normal; 252 | font-weight: 400; 253 | line-height: 2rem; 254 | /* 133.333% */ 255 | letter-spacing: -0.015rem; 256 | } 257 | 258 | .heading-3 { 259 | font-size: 1.5rem; 260 | font-style: normal; 261 | font-weight: 500; 262 | line-height: 2rem; 263 | /* 133.333% */ 264 | letter-spacing: -0.015rem; 265 | } 266 | 267 | /* scroll */ 268 | .hide-scrollbar::-webkit-scrollbar { 269 | display: none; 270 | } 271 | 272 | .hide-scrollbar { 273 | -ms-overflow-style: none; 274 | /* IE and Edge */ 275 | scrollbar-width: none; 276 | /* Firefox */ 277 | } 278 | 279 | .cta-button { 280 | display: inline-block; 281 | background-color: #0366d6; /* GitHub blue color */ 282 | color: white; 283 | padding: 10px 20px; 284 | border-radius: 6px; 285 | text-decoration: none; 286 | font-weight: bold; 287 | } 288 | 289 | .button-content { 290 | display: flex; 291 | align-items: center; 292 | justify-content: center; 293 | } 294 | 295 | .github-icon { 296 | width: 24px; 297 | height: 24px; 298 | margin-right: 10px; 299 | } -------------------------------------------------------------------------------- /css/tailwind.css: -------------------------------------------------------------------------------- 1 | /*! tailwindcss v3.4.10 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1440px){.container{max-width:1440px}}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.bottom-0{bottom:0}.bottom-\[calc\(100\%-300px\)\]{bottom:calc(100% - 300px)}.left-0{left:0}.left-1\/2{left:50%}.right-0{right:0}.top-0{top:0}.z-0{z-index:0}.z-10{z-index:10}.z-50{z-index:50}.z-\[1\]{z-index:1}.col-span-full{grid-column:1/-1}.mx-auto{margin-left:auto;margin-right:auto}.my-11{margin-top:2.75rem;margin-bottom:2.75rem}.-mt-8{margin-top:-2rem}.mb-12{margin-bottom:3rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.ml-auto{margin-left:auto}.mr-auto{margin-right:auto}.mt-12{margin-top:3rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline{display:inline}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.aspect-square{aspect-ratio:1/1}.aspect-video{aspect-ratio:16/9}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-16{height:4rem}.h-4{height:1rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-96{height:24rem}.h-\[2\.75rem\]{height:2.75rem}.h-\[420px\]{height:420px}.h-\[76px\]{height:76px}.h-auto{height:auto}.h-full{height:100%}.h-screen{height:100vh}.max-h-0{max-height:0}.max-h-\[1024px\]{max-height:1024px}.max-h-\[25rem\]{max-height:25rem}.max-h-fit{max-height:-moz-fit-content;max-height:fit-content}.min-h-\[720px\]{min-height:720px}.w-0{width:0}.w-11{width:2.75rem}.w-4{width:1rem}.w-6{width:1.5rem}.w-\[10\.59375rem\]{width:10.59375rem}.w-\[120\%\]{width:120%}.w-\[200\%\]{width:200%}.w-\[4\.5rem\]{width:4.5rem}.w-\[7\.5rem\]{width:7.5rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.min-w-\[900px\]{min-width:900px}.min-w-max{min-width:-moz-max-content;min-width:max-content}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-5xl{max-width:64rem}.max-w-7xl{max-width:80rem}.max-w-\[1440px\]{max-width:1440px}.max-w-\[2048px\]{max-width:2048px}.max-w-\[21rem\]{max-width:21rem}.max-w-\[32rem\]{max-width:32rem}.max-w-\[350px\]{max-width:350px}.max-w-\[36rem\]{max-width:36rem}.max-w-\[400px\]{max-width:400px}.max-w-\[40rem\]{max-width:40rem}.max-w-\[41rem\]{max-width:41rem}.max-w-\[50rem\]{max-width:50rem}.max-w-\[90rem\]{max-width:90rem}.max-w-full{max-width:100%}.max-w-none{max-width:none}.flex-1{flex:1 1 0%}.flex-none{flex:none}.flex-grow{flex-grow:1}.origin-center{transform-origin:center}.-translate-x-1\/2{--tw-translate-x:-50%}.-translate-x-1\/2,.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes negative-unbounce{0%{transform:rotate(-4deg)}to{transform:rotate(0deg)}}.animate-negative-unbounce{animation:negative-unbounce .2s ease-in-out forwards}@keyframes positive-unbounce{0%{transform:rotate(4deg)}to{transform:rotate(0deg)}}.animate-positive-unbounce{animation:positive-unbounce .2s ease-in-out forwards}.cursor-pointer{cursor:pointer}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.content-center{align-content:center}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-8{row-gap:2rem}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-3xl{border-radius:1.5rem}.rounded-\[0\.5rem\]{border-radius:.5rem}.rounded-\[1\.25rem\]{border-radius:1.25rem}.rounded-\[62\.5rem\]{border-radius:62.5rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-l-3xl{border-top-left-radius:1.5rem;border-bottom-left-radius:1.5rem}.rounded-l-\[0\.5rem\]{border-top-left-radius:.5rem;border-bottom-left-radius:.5rem}.rounded-r-\[0\.5rem\]{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}.rounded-t-3xl{border-top-left-radius:1.5rem;border-top-right-radius:1.5rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-white\/20{border-color:#fff3}.border-b-black\/30{border-bottom-color:#0000004d}.border-b-transparent{border-bottom-color:#0000}.border-b-white\/50{border-bottom-color:#ffffff80}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-black\/20{background-color:#0003}.bg-blue{--tw-bg-opacity:1;background-color:rgb(117 117 240/var(--tw-bg-opacity))}.bg-green{--tw-bg-opacity:1;background-color:rgb(60 221 140/var(--tw-bg-opacity))}.bg-grey{--tw-bg-opacity:1;background-color:rgb(243 243 243/var(--tw-bg-opacity))}.bg-inherit{background-color:inherit}.bg-pink{--tw-bg-opacity:1;background-color:rgb(230 153 217/var(--tw-bg-opacity))}.bg-purple{--tw-bg-opacity:1;background-color:rgb(58 34 52/var(--tw-bg-opacity))}.bg-purple\/10{background-color:#3a22341a}.bg-soft-blue{--tw-bg-opacity:1;background-color:rgb(232 232 252/var(--tw-bg-opacity))}.bg-soft-green{--tw-bg-opacity:1;background-color:rgb(212 247 230/var(--tw-bg-opacity))}.bg-soft-pink{--tw-bg-opacity:1;background-color:rgb(255 204 247/var(--tw-bg-opacity))}.bg-soft-purple{--tw-bg-opacity:1;background-color:rgb(237 222 233/var(--tw-bg-opacity))}.bg-soft-yellow{--tw-bg-opacity:1;background-color:rgb(255 238 204/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-white\/20{background-color:#fff3}.bg-white\/50{background-color:#ffffff80}.bg-white\/60{background-color:#fff9}.bg-yellow{--tw-bg-opacity:1;background-color:rgb(255 196 53/var(--tw-bg-opacity))}.bg-opacity-50{--tw-bg-opacity:0.5}.bg-gradient-to-b{background-image:linear-gradient(to bottom,var(--tw-gradient-stops))}.from-transparent{--tw-gradient-from:#0000 var(--tw-gradient-from-position);--tw-gradient-to:#0000 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-\[\#3a2234\]{--tw-gradient-to:#3a2234 var(--tw-gradient-to-position)}.p-2{padding:.5rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-16{padding-bottom:4rem}.pb-24{padding-bottom:6rem}.pl-5{padding-left:1.25rem}.pl-6{padding-left:1.5rem}.pr-20{padding-right:5rem}.pr-4{padding-right:1rem}.pt-8{padding-top:2rem}.text-center{text-align:center}.text-start{text-align:start}.font-geist{font-family:Geist,sans-serif}.text-2xl{font-size:1.5rem;line-height:2rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.text-black\/60{color:#0009}.text-black\/80{color:#000c}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-white\/80{color:#fffc}.text-opacity-60{--tw-text-opacity:0.6}.shadow-\[0_2px_2px_rgba\(255\2c 255\2c 255\2c 0\.5\)\2c 0_3px_3px_rgba\(0\2c 0\2c 0\2c 0\.2\)\]{--tw-shadow:0 2px 2px #ffffff80,0 3px 3px #0003;--tw-shadow-colored:0 2px 2px var(--tw-shadow-color),0 3px 3px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\[inset_0_2px_4px_rgba\(255\2c 255\2c 255\2c 0\.1\)\2c 0_4px_8px_rgba\(0\2c 0\2c 0\2c 0\.5\)\]{--tw-shadow:inset 0 2px 4px #ffffff1a,0 4px 8px #00000080;--tw-shadow-colored:inset 0 2px 4px var(--tw-shadow-color),0 4px 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.blur{--tw-blur:blur(8px)}.blur,.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-lg{--tw-backdrop-blur:blur(16px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.delay-\[300ms\]{transition-delay:.3s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.even\:bg-purple\/5:nth-child(2n){background-color:#3a22340d}.hover\:border-b-black\/80:hover{border-bottom-color:#000c}.hover\:border-b-white:hover{--tw-border-opacity:1;border-bottom-color:rgb(255 255 255/var(--tw-border-opacity))}.hover\:bg-black\/70:hover{background-color:#000000b3}.hover\:bg-black\/80:hover{background-color:#000c}.hover\:bg-white\/80:hover{background-color:#fffc}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.disabled\:opacity-40:disabled{opacity:.4}.group:hover .group-hover\:translate-x-\[-0\.34rem\]{--tw-translate-x:-0.34rem}.group:hover .group-hover\:translate-x-\[-0\.34rem\],.group:hover .group-hover\:translate-x-\[0\.29rem\]{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:translate-x-\[0\.29rem\]{--tw-translate-x:0.29rem}.group:hover .group-hover\:translate-y-\[-0\.13rem\]{--tw-translate-y:-0.13rem}.group:hover .group-hover\:translate-y-\[-0\.13rem\],.group:hover .group-hover\:translate-y-\[-0\.1rem\]{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:translate-y-\[-0\.1rem\]{--tw-translate-y:-0.1rem}.group:hover .group-hover\:translate-y-\[0\.57rem\]{--tw-translate-y:0.57rem}.group:hover .group-hover\:rotate-\[-4deg\],.group:hover .group-hover\:translate-y-\[0\.57rem\]{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:rotate-\[-4deg\]{--tw-rotate:-4deg}.group:hover .group-hover\:rotate-\[15deg\]{--tw-rotate:15deg}.group:hover .group-hover\:rotate-\[15deg\],.group:hover .group-hover\:rotate-\[20deg\]{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:rotate-\[20deg\]{--tw-rotate:20deg}.group:hover .group-hover\:rotate-\[4deg\]{--tw-rotate:4deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes negative-bounce{0%{transform:rotate(-4deg)}33%{transform:rotate(-3deg)}66%{transform:rotate(-5deg)}to{transform:rotate(-4deg)}}.group:hover .group-hover\:animate-negative-bounce{animation:negative-bounce .6s ease-in-out forwards}@keyframes positive-bounce{0%{transform:rotate(4deg)}33%{transform:rotate(3deg)}66%{transform:rotate(5deg)}to{transform:rotate(4deg)}}.group:hover .group-hover\:animate-positive-bounce{animation:positive-bounce .6s ease-in-out forwards}.peer\/collapsible:checked~.peer-checked\/collapsible\:max-h-\[30rem\]{max-height:30rem}.peer\/collapsible:checked~.peer-checked\/collapsible\:pb-4{padding-bottom:1rem}@media not all and (min-width:1024px){.max-lg\:basis-\[152px\]{flex-basis:152px}.max-lg\:flex-col{flex-direction:column}.max-lg\:items-start{align-items:flex-start}}@media (min-width:1024px){.lg\:-top-\[15\%\]{top:-15%}.lg\:mx-28{margin-left:7rem;margin-right:7rem}.lg\:mx-auto{margin-left:auto;margin-right:auto}.lg\:my-8{margin-top:2rem;margin-bottom:2rem}.lg\:-mr-16{margin-right:-4rem}.lg\:-mt-10{margin-top:-2.5rem}.lg\:-mt-16{margin-top:-4rem}.lg\:mb-8{margin-bottom:2rem}.lg\:mt-6{margin-top:1.5rem}.lg\:flex{display:flex}.lg\:h-14{height:3.5rem}.lg\:h-\[22rem\]{height:22rem}.lg\:h-\[600px\]{height:600px}.lg\:w-14{width:3.5rem}.lg\:w-96{width:24rem}.lg\:w-\[150\%\]{width:150%}.lg\:w-\[26rem\]{width:26rem}.lg\:max-w-7xl{max-width:80rem}.lg\:max-w-\[1440px\]{max-width:1440px}.lg\:max-w-\[43\.375rem\]{max-width:43.375rem}.lg\:max-w-\[45rem\]{max-width:45rem}.lg\:max-w-md{max-width:28rem}.lg\:max-w-xl{max-width:36rem}.lg\:flex-1{flex:1 1 0%}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:items-start{align-items:flex-start}.lg\:justify-between{justify-content:space-between}.lg\:gap-16{gap:4rem}.lg\:gap-6{gap:1.5rem}.lg\:gap-8{gap:2rem}.lg\:gap-x-12{-moz-column-gap:3rem;column-gap:3rem}.lg\:overflow-hidden{overflow:hidden}.lg\:rounded-t-\[2\.5rem\]{border-top-left-radius:2.5rem;border-top-right-radius:2.5rem}.lg\:p-12{padding:3rem}.lg\:p-8{padding:2rem}.lg\:px-16{padding-left:4rem;padding-right:4rem}.lg\:py-6{padding-top:1.5rem;padding-bottom:1.5rem}.lg\:pl-8{padding-left:2rem}.lg\:pr-6{padding-right:1.5rem}.lg\:pt-12{padding-top:3rem}.lg\:pt-16{padding-top:4rem}.peer\/collapsible:checked~.peer-checked\/collapsible\:lg\:pb-6{padding-bottom:1.5rem}}@media (min-width:1440px){.xl\:mx-auto{margin-left:auto;margin-right:auto}.xl\:flex{display:flex}.xl\:items-center{align-items:center}.xl\:overflow-hidden{overflow:hidden}.xl\:rounded-3xl{border-radius:1.5rem}.xl\:p-12{padding:3rem}} -------------------------------------------------------------------------------- /data/devcontainers.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/devcontainer-generator/0e00603b4163c0dd97e2ae450e5bf44576338ecd/data/devcontainers.db -------------------------------------------------------------------------------- /dependency_tree.txt: -------------------------------------------------------------------------------- 1 | async-timeout==4.0.3 2 | cloudscraper==1.2.71 3 | ├── pyparsing [required: >=2.4.7, installed: 3.1.2] 4 | ├── requests [required: >=2.9.2, installed: 2.32.3] 5 | │ ├── certifi [required: >=2017.4.17, installed: 2024.7.4] 6 | │ ├── charset-normalizer [required: >=2,<4, installed: 2.1.1] 7 | │ ├── idna [required: >=2.5,<4, installed: 3.7] 8 | │ └── urllib3 [required: >=1.21.1,<3, installed: 2.2.2] 9 | └── requests-toolbelt [required: >=0.9.1, installed: 1.0.0] 10 | └── requests [required: >=2.0.1,<3.0.0, installed: 2.32.3] 11 | ├── certifi [required: >=2017.4.17, installed: 2024.7.4] 12 | ├── charset-normalizer [required: >=2,<4, installed: 2.1.1] 13 | ├── idna [required: >=2.5,<4, installed: 3.7] 14 | └── urllib3 [required: >=1.21.1,<3, installed: 2.2.2] 15 | exceptiongroup==1.2.2 16 | GitPython==3.1.41 17 | └── gitdb [required: >=4.0.1,<5, installed: 4.0.11] 18 | └── smmap [required: >=3.0.1,<6, installed: 5.0.1] 19 | httptools==0.6.1 20 | instructor==1.3.7 21 | ├── aiohttp [required: >=3.9.1,<4.0.0, installed: 3.10.2] 22 | │ ├── aiohappyeyeballs [required: >=2.3.0, installed: 2.3.5] 23 | │ ├── aiosignal [required: >=1.1.2, installed: 1.3.1] 24 | │ │ └── frozenlist [required: >=1.1.0, installed: 1.4.1] 25 | │ ├── attrs [required: >=17.3.0, installed: 24.2.0] 26 | │ ├── frozenlist [required: >=1.1.1, installed: 1.4.1] 27 | │ ├── multidict [required: >=4.5,<7.0, installed: 6.0.5] 28 | │ └── yarl [required: >=1.0,<2.0, installed: 1.9.4] 29 | │ ├── idna [required: >=2.0, installed: 3.7] 30 | │ └── multidict [required: >=4.0, installed: 6.0.5] 31 | ├── docstring_parser [required: >=0.16,<0.17, installed: 0.16] 32 | ├── jiter [required: >=0.4.1,<0.5.0, installed: 0.4.2] 33 | ├── openai [required: >=1.1.0,<2.0.0, installed: 1.40.2] 34 | │ ├── anyio [required: >=3.5.0,<5, installed: 4.4.0] 35 | │ │ ├── idna [required: >=2.8, installed: 3.7] 36 | │ │ └── sniffio [required: >=1.1, installed: 1.3.1] 37 | │ ├── distro [required: >=1.7.0,<2, installed: 1.9.0] 38 | │ ├── httpx [required: >=0.23.0,<1, installed: 0.27.0] 39 | │ │ ├── anyio [required: Any, installed: 4.4.0] 40 | │ │ │ ├── idna [required: >=2.8, installed: 3.7] 41 | │ │ │ └── sniffio [required: >=1.1, installed: 1.3.1] 42 | │ │ ├── certifi [required: Any, installed: 2024.7.4] 43 | │ │ ├── httpcore [required: ==1.*, installed: 1.0.5] 44 | │ │ │ ├── certifi [required: Any, installed: 2024.7.4] 45 | │ │ │ └── h11 [required: >=0.13,<0.15, installed: 0.14.0] 46 | │ │ ├── idna [required: Any, installed: 3.7] 47 | │ │ └── sniffio [required: Any, installed: 1.3.1] 48 | │ ├── jiter [required: >=0.4.0,<1, installed: 0.4.2] 49 | │ ├── pydantic [required: >=1.9.0,<3, installed: 2.8.2] 50 | │ │ ├── annotated-types [required: >=0.4.0, installed: 0.7.0] 51 | │ │ ├── pydantic_core [required: ==2.20.1, installed: 2.20.1] 52 | │ │ │ └── typing_extensions [required: >=4.6.0,!=4.7.0, installed: 4.12.2] 53 | │ │ └── typing_extensions [required: >=4.6.1, installed: 4.12.2] 54 | │ ├── sniffio [required: Any, installed: 1.3.1] 55 | │ ├── tqdm [required: >4, installed: 4.66.5] 56 | │ └── typing_extensions [required: >=4.11,<5, installed: 4.12.2] 57 | ├── pydantic [required: >=2.8.0,<3.0.0, installed: 2.8.2] 58 | │ ├── annotated-types [required: >=0.4.0, installed: 0.7.0] 59 | │ ├── pydantic_core [required: ==2.20.1, installed: 2.20.1] 60 | │ │ └── typing_extensions [required: >=4.6.0,!=4.7.0, installed: 4.12.2] 61 | │ └── typing_extensions [required: >=4.6.1, installed: 4.12.2] 62 | ├── pydantic_core [required: >=2.18.0,<3.0.0, installed: 2.20.1] 63 | │ └── typing_extensions [required: >=4.6.0,!=4.7.0, installed: 4.12.2] 64 | ├── rich [required: >=13.7.0,<14.0.0, installed: 13.7.1] 65 | │ ├── markdown-it-py [required: >=2.2.0, installed: 3.0.0] 66 | │ │ └── mdurl [required: ~=0.1, installed: 0.1.2] 67 | │ └── Pygments [required: >=2.13.0,<3.0.0, installed: 2.18.0] 68 | ├── tenacity [required: >=8.4.1,<9.0.0, installed: 8.5.0] 69 | └── typer [required: >=0.9.0,<1.0.0, installed: 0.12.3] 70 | ├── click [required: >=8.0.0, installed: 8.1.7] 71 | ├── rich [required: >=10.11.0, installed: 13.7.1] 72 | │ ├── markdown-it-py [required: >=2.2.0, installed: 3.0.0] 73 | │ │ └── mdurl [required: ~=0.1, installed: 0.1.2] 74 | │ └── Pygments [required: >=2.13.0,<3.0.0, installed: 2.18.0] 75 | ├── shellingham [required: >=1.3.0, installed: 1.5.4] 76 | └── typing_extensions [required: >=3.7.4.3, installed: 4.12.2] 77 | ipykernel==6.29.5 78 | ├── comm [required: >=0.1.1, installed: 0.2.2] 79 | │ └── traitlets [required: >=4, installed: 5.14.3] 80 | ├── debugpy [required: >=1.6.5, installed: 1.8.5] 81 | ├── ipython [required: >=7.23.1, installed: 8.26.0] 82 | │ ├── decorator [required: Any, installed: 5.1.1] 83 | │ ├── jedi [required: >=0.16, installed: 0.19.1] 84 | │ │ └── parso [required: >=0.8.3,<0.9.0, installed: 0.8.4] 85 | │ ├── matplotlib-inline [required: Any, installed: 0.1.7] 86 | │ │ └── traitlets [required: Any, installed: 5.14.3] 87 | │ ├── pexpect [required: >4.3, installed: 4.9.0] 88 | │ │ └── ptyprocess [required: >=0.5, installed: 0.7.0] 89 | │ ├── prompt_toolkit [required: >=3.0.41,<3.1.0, installed: 3.0.47] 90 | │ │ └── wcwidth [required: Any, installed: 0.2.13] 91 | │ ├── Pygments [required: >=2.4.0, installed: 2.18.0] 92 | │ ├── stack-data [required: Any, installed: 0.6.3] 93 | │ │ ├── asttokens [required: >=2.1.0, installed: 2.4.1] 94 | │ │ │ └── six [required: >=1.12.0, installed: 1.16.0] 95 | │ │ ├── executing [required: >=1.2.0, installed: 2.0.1] 96 | │ │ └── pure_eval [required: Any, installed: 0.2.3] 97 | │ └── traitlets [required: >=5.13.0, installed: 5.14.3] 98 | ├── jupyter_client [required: >=6.1.12, installed: 8.6.2] 99 | │ ├── jupyter_core [required: >=4.12,!=5.0.*, installed: 5.7.2] 100 | │ │ ├── platformdirs [required: >=2.5, installed: 4.2.2] 101 | │ │ └── traitlets [required: >=5.3, installed: 5.14.3] 102 | │ ├── python-dateutil [required: >=2.8.2, installed: 2.9.0.post0] 103 | │ │ └── six [required: >=1.5, installed: 1.16.0] 104 | │ ├── pyzmq [required: >=23.0, installed: 26.1.0] 105 | │ ├── tornado [required: >=6.2, installed: 6.4.1] 106 | │ └── traitlets [required: >=5.3, installed: 5.14.3] 107 | ├── jupyter_core [required: >=4.12,!=5.0.*, installed: 5.7.2] 108 | │ ├── platformdirs [required: >=2.5, installed: 4.2.2] 109 | │ └── traitlets [required: >=5.3, installed: 5.14.3] 110 | ├── matplotlib-inline [required: >=0.1, installed: 0.1.7] 111 | │ └── traitlets [required: Any, installed: 5.14.3] 112 | ├── nest-asyncio [required: Any, installed: 1.6.0] 113 | ├── packaging [required: Any, installed: 24.1] 114 | ├── psutil [required: Any, installed: 6.0.0] 115 | ├── pyzmq [required: >=24, installed: 26.1.0] 116 | ├── tornado [required: >=6.1, installed: 6.4.1] 117 | └── traitlets [required: >=5.4.0, installed: 5.14.3] 118 | Jinja2==3.1.4 119 | └── MarkupSafe [required: >=2.0, installed: 2.1.5] 120 | jsonschema==4.23.0 121 | ├── attrs [required: >=22.2.0, installed: 24.2.0] 122 | ├── jsonschema-specifications [required: >=2023.03.6, installed: 2023.12.1] 123 | │ └── referencing [required: >=0.31.0, installed: 0.35.1] 124 | │ ├── attrs [required: >=22.2.0, installed: 24.2.0] 125 | │ └── rpds-py [required: >=0.7.0, installed: 0.20.0] 126 | ├── referencing [required: >=0.28.4, installed: 0.35.1] 127 | │ ├── attrs [required: >=22.2.0, installed: 24.2.0] 128 | │ └── rpds-py [required: >=0.7.0, installed: 0.20.0] 129 | └── rpds-py [required: >=0.7.1, installed: 0.20.0] 130 | pipdeptree==2.23.1 131 | ├── packaging [required: >=23.1, installed: 24.1] 132 | └── pip [required: >=23.1.2, installed: 24.2] 133 | python-dotenv==1.0.1 134 | python-fasthtml==0.2.4 135 | ├── beautifulsoup4 [required: Any, installed: 4.12.3] 136 | │ └── soupsieve [required: >1.2, installed: 2.5] 137 | ├── fastcore [required: >=1.6.3, installed: 1.6.3] 138 | │ └── packaging [required: Any, installed: 24.1] 139 | ├── fastlite [required: >=0.0.6, installed: 0.0.7] 140 | │ ├── fastcore [required: >=1.5.41, installed: 1.6.3] 141 | │ │ └── packaging [required: Any, installed: 24.1] 142 | │ └── sqlite_minutils [required: Any, installed: 3.36.0.post4] 143 | │ └── fastcore [required: Any, installed: 1.6.3] 144 | │ └── packaging [required: Any, installed: 24.1] 145 | ├── httpx [required: Any, installed: 0.27.0] 146 | │ ├── anyio [required: Any, installed: 4.4.0] 147 | │ │ ├── idna [required: >=2.8, installed: 3.7] 148 | │ │ └── sniffio [required: >=1.1, installed: 1.3.1] 149 | │ ├── certifi [required: Any, installed: 2024.7.4] 150 | │ ├── httpcore [required: ==1.*, installed: 1.0.5] 151 | │ │ ├── certifi [required: Any, installed: 2024.7.4] 152 | │ │ └── h11 [required: >=0.13,<0.15, installed: 0.14.0] 153 | │ ├── idna [required: Any, installed: 3.7] 154 | │ └── sniffio [required: Any, installed: 1.3.1] 155 | ├── itsdangerous [required: Any, installed: 2.2.0] 156 | ├── oauthlib [required: Any, installed: 3.2.2] 157 | ├── python-dateutil [required: Any, installed: 2.9.0.post0] 158 | │ └── six [required: >=1.5, installed: 1.16.0] 159 | ├── python-multipart [required: Any, installed: 0.0.9] 160 | ├── starlette [required: >0.33, installed: 0.38.2] 161 | │ └── anyio [required: >=3.4.0,<5, installed: 4.4.0] 162 | │ ├── idna [required: >=2.8, installed: 3.7] 163 | │ └── sniffio [required: >=1.1, installed: 1.3.1] 164 | └── uvicorn [required: >=0.30, installed: 0.30.5] 165 | ├── click [required: >=7.0, installed: 8.1.7] 166 | └── h11 [required: >=0.8, installed: 0.14.0] 167 | PyYAML==6.0.2 168 | replicate==0.31.0 169 | ├── httpx [required: >=0.21.0,<1, installed: 0.27.0] 170 | │ ├── anyio [required: Any, installed: 4.4.0] 171 | │ │ ├── idna [required: >=2.8, installed: 3.7] 172 | │ │ └── sniffio [required: >=1.1, installed: 1.3.1] 173 | │ ├── certifi [required: Any, installed: 2024.7.4] 174 | │ ├── httpcore [required: ==1.*, installed: 1.0.5] 175 | │ │ ├── certifi [required: Any, installed: 2024.7.4] 176 | │ │ └── h11 [required: >=0.13,<0.15, installed: 0.14.0] 177 | │ ├── idna [required: Any, installed: 3.7] 178 | │ └── sniffio [required: Any, installed: 1.3.1] 179 | ├── packaging [required: Any, installed: 24.1] 180 | ├── pydantic [required: >1.10.7, installed: 2.8.2] 181 | │ ├── annotated-types [required: >=0.4.0, installed: 0.7.0] 182 | │ ├── pydantic_core [required: ==2.20.1, installed: 2.20.1] 183 | │ │ └── typing_extensions [required: >=4.6.0,!=4.7.0, installed: 4.12.2] 184 | │ └── typing_extensions [required: >=4.6.1, installed: 4.12.2] 185 | └── typing_extensions [required: >=4.5.0, installed: 4.12.2] 186 | setuptools==69.0.3 187 | SQLAlchemy==2.0.32 188 | ├── greenlet [required: !=0.4.17, installed: 3.0.3] 189 | └── typing_extensions [required: >=4.6.0, installed: 4.12.2] 190 | sqlite-vec==0.1.1 191 | tiktoken==0.7.0 192 | ├── regex [required: >=2022.1.18, installed: 2024.7.24] 193 | └── requests [required: >=2.26.0, installed: 2.32.3] 194 | ├── certifi [required: >=2017.4.17, installed: 2024.7.4] 195 | ├── charset-normalizer [required: >=2,<4, installed: 2.1.1] 196 | ├── idna [required: >=2.5,<4, installed: 3.7] 197 | └── urllib3 [required: >=1.21.1,<3, installed: 2.2.2] 198 | uvloop==0.19.0 199 | watchfiles==0.23.0 200 | └── anyio [required: >=3.0.0, installed: 4.4.0] 201 | ├── idna [required: >=2.8, installed: 3.7] 202 | └── sniffio [required: >=1.1, installed: 1.3.1] 203 | websockets==12.0 204 | wheel==0.44.0 205 | -------------------------------------------------------------------------------- /favicon-dark.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/devcontainer-generator/0e00603b4163c0dd97e2ae450e5bf44576338ecd/favicon-dark.ico -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daytonaio/devcontainer-generator/0e00603b4163c0dd97e2ae450e5bf44576338ecd/favicon.ico -------------------------------------------------------------------------------- /helpers/__init__.py: -------------------------------------------------------------------------------- 1 | # This file can be left empty or you can add imports if you want to make certain functions easily accessible -------------------------------------------------------------------------------- /helpers/devcontainer_helpers.py: -------------------------------------------------------------------------------- 1 | # helpers/devcontainer_helpers.py 2 | 3 | import json 4 | import logging 5 | import os 6 | import jsonschema 7 | import tiktoken 8 | from helpers.jinja_helper import process_template 9 | from schemas import DevContainerModel 10 | from supabase_client import supabase 11 | from models import DevContainer 12 | 13 | 14 | import logging 15 | import tiktoken 16 | 17 | def truncate_context(context, max_tokens=120000): 18 | logging.info(f"Starting truncate_context with max_tokens={max_tokens}") 19 | logging.debug(f"Initial context length: {len(context)} characters") 20 | 21 | encoding = tiktoken.encoding_for_model("gpt-4o-mini") 22 | tokens = encoding.encode(context) 23 | 24 | logging.info(f"Initial token count: {len(tokens)}") 25 | 26 | if len(tokens) <= max_tokens: 27 | logging.info("Context is already within token limit. No truncation needed.") 28 | return context 29 | 30 | logging.info(f"Context size is {len(tokens)} tokens. Truncation needed.") 31 | 32 | # Prioritize keeping the repository structure and languages 33 | structure_end = context.find("<>") 34 | languages_end = context.find("<>") 35 | 36 | logging.debug(f"Structure end position: {structure_end}") 37 | logging.debug(f"Languages end position: {languages_end}") 38 | 39 | important_content = context[:languages_end] + "<>\n\n" 40 | remaining_content = context[languages_end + len("<>\n\n"):] 41 | 42 | important_tokens = encoding.encode(important_content) 43 | logging.debug(f"Important content token count: {len(important_tokens)}") 44 | 45 | if len(important_tokens) > max_tokens: 46 | logging.warning("Important content alone exceeds max_tokens. Truncating important content.") 47 | important_content = encoding.decode(important_tokens[:max_tokens]) 48 | return important_content 49 | 50 | remaining_tokens = max_tokens - len(important_tokens) 51 | logging.info(f"Tokens available for remaining content: {remaining_tokens}") 52 | 53 | truncated_remaining = encoding.decode(encoding.encode(remaining_content)[:remaining_tokens]) 54 | 55 | final_context = important_content + truncated_remaining 56 | final_tokens = encoding.encode(final_context) 57 | 58 | logging.info(f"Final token count: {len(final_tokens)}") 59 | logging.debug(f"Final context length: {len(final_context)} characters") 60 | 61 | return final_context 62 | 63 | def generate_devcontainer_json(instructor_client, repo_url, repo_context, devcontainer_url=None, max_retries=2, regenerate=False): 64 | existing_devcontainer = None 65 | if "<>" in repo_context: 66 | logging.info("Existing devcontainer.json found in the repository.") 67 | existing_devcontainer = ( 68 | repo_context.split("<>")[1] 69 | .split("<>")[0] 70 | .strip() 71 | ) 72 | if not regenerate and devcontainer_url: 73 | logging.info(f"Using existing devcontainer.json from URL: {devcontainer_url}") 74 | return existing_devcontainer, devcontainer_url 75 | 76 | logging.info("Generating devcontainer.json...") 77 | 78 | # Truncate the context to fit within token limits 79 | truncated_context = truncate_context(repo_context, max_tokens=126000) 80 | 81 | template_data = { 82 | "repo_url": repo_url, 83 | "repo_context": truncated_context, 84 | "existing_devcontainer": existing_devcontainer 85 | } 86 | 87 | prompt = process_template("prompts/devcontainer.jinja", template_data) 88 | 89 | for attempt in range(max_retries + 1): 90 | try: 91 | logging.debug(f"Attempt {attempt + 1} to generate devcontainer.json") 92 | response = instructor_client.chat.completions.create( 93 | model=os.getenv("MODEL"), 94 | response_model=DevContainerModel, 95 | messages=[ 96 | {"role": "system", "content": "You are a helpful assistant that generates devcontainer.json files."}, 97 | {"role": "user", "content": prompt}, 98 | ], 99 | ) 100 | devcontainer_json = json.dumps(response.dict(exclude_none=True), indent=2) 101 | 102 | if validate_devcontainer_json(devcontainer_json): 103 | logging.info("Successfully generated and validated devcontainer.json") 104 | if existing_devcontainer and not regenerate: 105 | return existing_devcontainer, devcontainer_url 106 | else: 107 | return devcontainer_json, None # Return None as URL for generated content 108 | else: 109 | logging.warning(f"Generated JSON failed validation on attempt {attempt + 1}") 110 | if attempt == max_retries: 111 | raise ValueError("Failed to generate valid devcontainer.json after maximum retries") 112 | except Exception as e: 113 | logging.error(f"Error on attempt {attempt + 1}: {str(e)}") 114 | if attempt == max_retries: 115 | raise 116 | 117 | raise ValueError("Failed to generate valid devcontainer.json after maximum retries") 118 | 119 | def validate_devcontainer_json(devcontainer_json): 120 | logging.info("Validating devcontainer.json...") 121 | schema_path = os.path.join(os.path.dirname(__file__), "..", "schemas", "devContainer.base.schema.json") 122 | with open(schema_path, "r") as schema_file: 123 | schema = json.load(schema_file) 124 | try: 125 | logging.debug("Running validation...") 126 | jsonschema.validate(instance=json.loads(devcontainer_json), schema=schema) 127 | logging.info("Validation successful.") 128 | return True 129 | except jsonschema.exceptions.ValidationError as e: 130 | logging.error(f"Validation failed: {e}") 131 | return False 132 | 133 | def save_devcontainer(new_devcontainer): 134 | try: 135 | result = supabase.table("devcontainers").insert(new_devcontainer.dict()).execute() 136 | return result.data[0] if result.data else None 137 | except Exception as e: 138 | logging.error(f"Error saving devcontainer to Supabase: {str(e)}") 139 | raise -------------------------------------------------------------------------------- /helpers/github_helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import logging 4 | import requests 5 | from helpers.token_helpers import count_tokens 6 | from models import DevContainer 7 | from supabase_client import supabase 8 | 9 | def is_valid_github_url(url): 10 | pattern = r"^https?://github\.com/[\w-]+/[\w.-]+/?$" 11 | return re.match(pattern, url) is not None 12 | 13 | def fetch_repo_context(repo_url, max_depth=1): 14 | # First, check if the URL is valid 15 | if not is_valid_github_url(repo_url): 16 | logging.error(f"Invalid GitHub repository URL: {repo_url}") 17 | raise ValueError("Invalid GitHub repository URL") 18 | 19 | logging.info(f"Fetching context from GitHub repository: {repo_url}") 20 | token = os.getenv("GITHUB_TOKEN") 21 | 22 | parts = repo_url.split("/") 23 | owner = parts[-2] 24 | repo = parts[-1] 25 | 26 | contents_api_url = f"https://api.github.com/repos/{owner}/{repo}/contents" 27 | languages_api_url = f"https://api.github.com/repos/{owner}/{repo}/languages" 28 | 29 | headers = { 30 | "Accept": "application/vnd.github.v3+json", 31 | "Authorization": f"token {token}", 32 | } 33 | 34 | existing_devcontainer = None 35 | devcontainer_url = None 36 | 37 | root_devcontainer_url = f"{contents_api_url}/.devcontainer.json" 38 | response = requests.get(root_devcontainer_url, headers=headers) 39 | if response.status_code == 200: 40 | existing_devcontainer = requests.get(response.json()["download_url"]).text 41 | devcontainer_url = response.json()["download_url"] 42 | 43 | if not existing_devcontainer: 44 | devcontainer_dir_url = f"{contents_api_url}/.devcontainer" 45 | response = requests.get(devcontainer_dir_url, headers=headers) 46 | if response.status_code == 200: 47 | for item in response.json(): 48 | if item["name"] == "devcontainer.json": 49 | existing_devcontainer = requests.get(item["download_url"]).text 50 | devcontainer_url = item["download_url"] 51 | break 52 | 53 | context = [] 54 | total_tokens = 0 55 | 56 | def traverse_dir(api_url, depth=0, prefix=""): 57 | if depth > max_depth: 58 | return [] 59 | 60 | logging.info(f"Traversing directory: {api_url}") 61 | response = requests.get(api_url, headers=headers) 62 | response.raise_for_status() 63 | 64 | structure = [] 65 | important_files = [ 66 | "requirements.txt", "Dockerfile", ".gitignore", "package.json", 67 | "Gemfile", "README.md", ".env.example", "Pipfile", "setup.py", 68 | "Pipfile.lock", "pyproject.toml", "CMakeLists.txt", "Makefile", 69 | "go.mod", "go.sum", "pom.xml", "build.gradle", "Cargo.toml", 70 | "Cargo.lock", "composer.json", "phpunit.xml", "mix.exs", 71 | "pubspec.yaml", "stack.yaml", "DESCRIPTION", "NAMESPACE", "Rakefile", 72 | ] 73 | large_dirs_to_skip = ["node_modules", "vendor"] 74 | 75 | for item in response.json(): 76 | logging.debug(f"Processing item: {item['name']}") 77 | if item["type"] == "dir": 78 | if item["name"] in large_dirs_to_skip: 79 | continue 80 | structure.append(f"{prefix}{item['name']}/") 81 | structure.extend( 82 | traverse_dir(item["url"], depth + 1, prefix=prefix + " ") 83 | ) 84 | else: 85 | structure.append(f"{prefix}{item['name']}") 86 | 87 | if item["type"] == "file" and item["name"] in important_files: 88 | logging.debug(f"Fetching content of {item['name']}") 89 | file_content = requests.get(item["download_url"]).text 90 | content_text = ( 91 | f"<>\n{file_content}" 92 | + f"\n<>" 93 | ) 94 | context.append(content_text) 95 | 96 | file_tokens = count_tokens(content_text) 97 | nonlocal total_tokens 98 | total_tokens += file_tokens 99 | 100 | return structure 101 | 102 | logging.info("Building repository structure...") 103 | repo_structure = traverse_dir(contents_api_url) 104 | 105 | logging.info("Adding repository structure to context...") 106 | repo_structure_text = ( 107 | "<>\n" 108 | + "\n".join(repo_structure) 109 | + "\n<>" 110 | ) 111 | context.insert(0, repo_structure_text) 112 | 113 | repo_structure_tokens = count_tokens(repo_structure_text) 114 | total_tokens += repo_structure_tokens 115 | 116 | logging.info("Fetching repository languages...") 117 | languages_response = requests.get(languages_api_url, headers=headers) 118 | languages_response.raise_for_status() 119 | 120 | logging.info("Adding repository languages to context...") 121 | languages_data = languages_response.json() 122 | languages_context = ( 123 | "<>\n" 124 | + "\n".join( 125 | [f"{lang}: {count} lines" for lang, count in languages_data.items()] 126 | ) 127 | + "\n<>" 128 | ) 129 | context.append(languages_context) 130 | 131 | languages_tokens = count_tokens(languages_context) 132 | total_tokens += languages_tokens 133 | 134 | logging.debug(f"Total tokens: {total_tokens}") 135 | 136 | if existing_devcontainer: 137 | devcontainer_context = ( 138 | "<>\n" 139 | f"{existing_devcontainer}\n" 140 | "<>" 141 | ) 142 | context.append(devcontainer_context) 143 | total_tokens += count_tokens(devcontainer_context) 144 | 145 | return "\n\n".join(context), existing_devcontainer, devcontainer_url 146 | 147 | def check_url_exists(url): 148 | existing = supabase.table("devcontainers").select("*").eq("url", url).order("created_at", desc=True).limit(1).execute() 149 | existing_record = existing.data[0] if existing.data else None 150 | return existing_record is not None, existing_record -------------------------------------------------------------------------------- /helpers/jinja_helper.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from jinja2 import Environment, FileSystemLoader, select_autoescape 4 | 5 | 6 | def process_template(template_file: str, data: dict[str, Any]) -> str: 7 | jinja_env = Environment( 8 | loader=FileSystemLoader(searchpath="./"), autoescape=select_autoescape() 9 | ) 10 | template = jinja_env.get_template(template_file) 11 | return template.render(**data) -------------------------------------------------------------------------------- /helpers/openai_helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from openai import AzureOpenAI 4 | import instructor 5 | 6 | def setup_azure_openai(): 7 | logging.info("Setting up Azure OpenAI client...") 8 | return AzureOpenAI( 9 | api_key=os.getenv("AZURE_OPENAI_API_KEY"), 10 | azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"), 11 | api_version=os.getenv("AZURE_OPENAI_API_VERSION"), 12 | ) 13 | 14 | def setup_instructor(openai_client): 15 | logging.info("Setting up Instructor client...") 16 | return instructor.patch(openai_client) 17 | 18 | def check_env_vars(): 19 | required_vars = [ 20 | "AZURE_OPENAI_ENDPOINT", 21 | "AZURE_OPENAI_API_KEY", 22 | "AZURE_OPENAI_API_VERSION", 23 | "MODEL", 24 | "GITHUB_TOKEN", 25 | ] 26 | missing_vars = [var for var in required_vars if not os.environ.get(var)] 27 | if missing_vars: 28 | print( 29 | f"Missing environment variables: {', '.join(missing_vars)}. " 30 | "Please configure the env vars file properly." 31 | ) 32 | return False 33 | return True -------------------------------------------------------------------------------- /helpers/token_helpers.py: -------------------------------------------------------------------------------- 1 | import tiktoken 2 | 3 | def count_tokens(text): 4 | encoder = tiktoken.encoding_for_model("gpt-4o") 5 | tokens = encoder.encode(text) 6 | return len(tokens) 7 | 8 | def truncate_to_token_limit(text, model_name, max_tokens): 9 | encoding = tiktoken.encoding_for_model(model_name) 10 | tokens = encoding.encode(text) 11 | if len(tokens) > max_tokens: 12 | truncated_tokens = tokens[:max_tokens] 13 | return encoding.decode(truncated_tokens) 14 | return text -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | console.log("main.js loaded"); 2 | 3 | document.addEventListener('htmx:beforeRequest', function(event) { 4 | console.log('htmx:beforeRequest triggered'); 5 | 6 | if (event.detail.elt.id === 'generate-button') { 7 | console.log('Disabling generate button'); 8 | event.detail.elt.disabled = true; 9 | } 10 | }); 11 | 12 | document.addEventListener('htmx:afterRequest', function(event) { 13 | console.log('htmx:afterRequest triggered'); 14 | 15 | if (event.detail.elt.id === 'generate-button') { 16 | console.log('Enabling generate button'); 17 | event.detail.elt.disabled = false; 18 | } 19 | }); 20 | 21 | document.addEventListener('DOMContentLoaded', function() { 22 | console.log('DOM fully loaded and parsed'); 23 | const generateButton = document.getElementById('generate-button'); 24 | if (generateButton) { 25 | console.log('Generate button found'); 26 | generateButton.addEventListener('click', function() { 27 | console.log('Generate button clicked'); 28 | }); 29 | } else { 30 | console.log('Generate button not found'); 31 | } 32 | }); 33 | 34 | // Remove the initializeButtons function and related code since we're not using it anymore 35 | 36 | function handleCopyClick(event) { 37 | const button = event.target.closest('.icon-button.copy-button'); 38 | if (!button) return; 39 | 40 | console.log("Copy button clicked"); 41 | 42 | const codeContainer = button.closest(".code-container"); 43 | if (!codeContainer) { 44 | console.error("Code container not found"); 45 | return; 46 | } 47 | 48 | const codeElement = codeContainer.querySelector("code"); 49 | if (!codeElement) { 50 | console.error("Code element not found"); 51 | return; 52 | } 53 | 54 | const codeContents = codeElement.textContent; 55 | const copiedText = codeContainer.querySelector("#copied-text"); 56 | 57 | navigator.clipboard.writeText(codeContents) 58 | .then(() => { 59 | console.log("Copy successful"); 60 | showActionText("Copied!"); 61 | }) 62 | .catch(error => { 63 | console.error("Copy failed:", error); 64 | }); 65 | } 66 | 67 | function handleRegenerateClick(event) { 68 | const button = event.target.closest('.icon-button.regenerate-button'); 69 | if (!button) return; 70 | 71 | console.log("Regenerate button clicked"); 72 | showActionText("Regenerating..."); 73 | // The actual regeneration is handled by HTMX 74 | } 75 | 76 | function showActionText(text) { 77 | const actionText = document.getElementById('action-text'); 78 | if (actionText) { 79 | actionText.textContent = text; 80 | actionText.style.opacity = "1"; 81 | if (text !== "Regenerating...") { 82 | setTimeout(() => { 83 | actionText.style.opacity = "0"; 84 | }, 2000); 85 | } 86 | } 87 | } 88 | 89 | function initializeButtons() { 90 | const copyButtons = document.querySelectorAll(".icon-button.copy-button"); 91 | const regenerateButtons = document.querySelectorAll(".icon-button.regenerate-button"); 92 | 93 | console.log("Copy buttons found:", copyButtons.length); 94 | console.log("Regenerate buttons found:", regenerateButtons.length); 95 | 96 | copyButtons.forEach((button, index) => { 97 | console.log(`Adding listener to copy button ${index}`); 98 | button.removeEventListener("click", handleCopyClick); 99 | button.addEventListener("click", handleCopyClick); 100 | }); 101 | 102 | regenerateButtons.forEach((button, index) => { 103 | console.log(`Adding listener to regenerate button ${index}`); 104 | button.removeEventListener("click", handleRegenerateClick); 105 | button.addEventListener("click", handleRegenerateClick); 106 | }); 107 | } 108 | 109 | function setupObserver() { 110 | const observer = new MutationObserver((mutations) => { 111 | mutations.forEach((mutation) => { 112 | if (mutation.type === 'childList') { 113 | initializeButtons(); 114 | } 115 | }); 116 | }); 117 | 118 | observer.observe(document.body, { childList: true, subtree: true }); 119 | } 120 | 121 | function isValidGithubUrl(url) { 122 | const pattern = /^https?:\/\/github\.com\/[\w-]+\/[\w.-]+\/?$/; 123 | return pattern.test(url); 124 | } 125 | 126 | function validateRepoUrl() { 127 | const input = document.querySelector('input[name="repo_url"]'); 128 | const errorDiv = document.getElementById('url-error'); 129 | const generateButton = document.getElementById('generate-button'); 130 | 131 | if (input.value.trim() === '') { 132 | errorDiv.textContent = 'Please enter a GitHub repository URL.'; 133 | generateButton.disabled = true; 134 | return false; 135 | } 136 | 137 | if (!isValidGithubUrl(input.value)) { 138 | errorDiv.textContent = 'Please enter a valid GitHub repository URL.'; 139 | generateButton.disabled = true; 140 | return false; 141 | } 142 | 143 | errorDiv.textContent = ''; 144 | generateButton.disabled = false; 145 | return true; 146 | } 147 | 148 | document.addEventListener('DOMContentLoaded', function() { 149 | const repoUrlInput = document.querySelector('input[name="repo_url"]'); 150 | repoUrlInput.addEventListener('input', validateRepoUrl); 151 | 152 | const generateForm = document.getElementById('generate-form'); 153 | generateForm.addEventListener('submit', function(event) { 154 | if (!validateRepoUrl()) { 155 | event.preventDefault(); 156 | } 157 | }); 158 | 159 | document.body.addEventListener('click', (event) => { 160 | handleCopyClick(event); 161 | handleRegenerateClick(event); 162 | }); 163 | 164 | initializeButtons(); 165 | setupObserver(); 166 | }); 167 | 168 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import json 4 | from datetime import datetime 5 | from fasthtml.common import * 6 | from dotenv import load_dotenv 7 | from supabase_client import supabase 8 | 9 | from helpers.openai_helpers import setup_azure_openai, setup_instructor 10 | from helpers.github_helpers import fetch_repo_context, check_url_exists 11 | from helpers.devcontainer_helpers import generate_devcontainer_json, validate_devcontainer_json 12 | from helpers.token_helpers import count_tokens, truncate_to_token_limit 13 | from models import DevContainer 14 | from schemas import DevContainerModel 15 | from content import * 16 | 17 | # Set up logging 18 | logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s") 19 | 20 | # Load environment variables 21 | load_dotenv() 22 | 23 | def check_env_vars(): 24 | required_vars = [ 25 | "AZURE_OPENAI_ENDPOINT", 26 | "AZURE_OPENAI_API_KEY", 27 | "AZURE_OPENAI_API_VERSION", 28 | "MODEL", 29 | "GITHUB_TOKEN", 30 | "SUPABASE_URL", 31 | "SUPABASE_KEY", 32 | ] 33 | missing_vars = [var for var in required_vars if not os.environ.get(var)] 34 | if missing_vars: 35 | print(f"Missing environment variables: {', '.join(missing_vars)}. Please configure the env vars file properly.") 36 | return False 37 | return True 38 | 39 | hdrs = [ 40 | Script(src="https://www.googletagmanager.com/gtag/js?id=G-Q22LCTCW8Y", aync=True), 41 | Script(""" 42 | window.dataLayer = window.dataLayer || []; 43 | function gtag(){dataLayer.push(arguments);} 44 | gtag('js', new Date()); 45 | gtag('config', 'G-Q22LCTCW8Y'); 46 | """), 47 | Script(""" 48 | (function(c,l,a,r,i,t,y){ 49 | c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)}; 50 | t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i; 51 | y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y); 52 | })(window, document, "clarity", "script", "o5om7ajkg6"); 53 | """), 54 | picolink, 55 | Meta(charset='UTF-8'), 56 | Meta(name='viewport', content='width=device-width, initial-scale=1.0, maximum-scale=1.0'), 57 | Meta(name='description', content=description), 58 | *Favicon('favicon.ico', 'favicon-dark.ico'), 59 | *Socials(title='DevContainer.ai', 60 | description=description, 61 | site_name='devcontainer.ai', 62 | twitter_site='@daytonaio', 63 | image=f'/assets/og-sq.png', 64 | url=''), 65 | Script(src='https://cdn.jsdelivr.net/gh/gnat/surreal@main/surreal.js'), 66 | scopesrc, 67 | Link(rel="stylesheet", href="/css/main.css"), 68 | ] 69 | 70 | # Initialize FastHTML app 71 | app, rt = fast_app( 72 | hdrs=hdrs, 73 | live=True, 74 | debug=True 75 | ) 76 | 77 | scripts = ( 78 | Script(src="/js/main.js"), 79 | ) 80 | 81 | from fastcore.xtras import timed_cache 82 | 83 | # Main page composition 84 | @timed_cache(seconds=60) 85 | def home(): 86 | return (Title(f"DevContainer.ai - {description}"), 87 | Main( 88 | hero_section(), 89 | generator_section(), 90 | setup_section(), 91 | manifesto(), 92 | benefits_section(), 93 | examples_section(), 94 | faq_section(), 95 | cta_section(), 96 | footer_section()), 97 | *scripts) 98 | 99 | # Define routes 100 | @rt("/") 101 | async def get(): 102 | return home() 103 | 104 | @rt("/generate", methods=["post"]) 105 | async def post(repo_url: str, regenerate: bool = False): 106 | logging.info(f"Generating devcontainer.json for: {repo_url}") 107 | 108 | # Normalize the repo_url by stripping trailing slashes 109 | repo_url = repo_url.rstrip('/') 110 | 111 | try: 112 | exists, existing_record = check_url_exists(repo_url) 113 | logging.info(f"URL check result: exists={exists}, existing_record={existing_record}") 114 | 115 | repo_context, existing_devcontainer, devcontainer_url = fetch_repo_context(repo_url) 116 | logging.info(f"Fetched repo context. Existing devcontainer: {'Yes' if existing_devcontainer else 'No'}") 117 | logging.info(f"Devcontainer URL: {devcontainer_url}") 118 | 119 | if exists and not regenerate: 120 | logging.info(f"URL already exists in database. Returning existing devcontainer_json for: {repo_url}") 121 | devcontainer_json = existing_record['devcontainer_json'] 122 | generated = existing_record['generated'] 123 | source = "database" 124 | url = existing_record['devcontainer_url'] 125 | else: 126 | devcontainer_json, url = generate_devcontainer_json(instructor_client, repo_url, repo_context, devcontainer_url, regenerate=regenerate) 127 | generated = True 128 | source = "generated" if url is None else "repository" 129 | 130 | 131 | if not exists or regenerate: 132 | logging.info("Saving to database...") 133 | try: 134 | if hasattr(openai_client.embeddings, "create"): 135 | embedding_model = os.getenv("EMBEDDING", "text-embedding-ada-002") 136 | max_tokens = int(os.getenv("EMBEDDING_MODEL_MAX_TOKENS", 8192)) 137 | 138 | truncated_context = truncate_to_token_limit(repo_context, embedding_model, max_tokens) 139 | 140 | embedding = openai_client.embeddings.create(input=truncated_context, model=embedding_model).data[0].embedding 141 | embedding_json = json.dumps(embedding) 142 | else: 143 | embedding_json = None 144 | 145 | new_devcontainer = DevContainer( 146 | url=repo_url, 147 | devcontainer_json=devcontainer_json, 148 | devcontainer_url=devcontainer_url, 149 | repo_context=repo_context, 150 | tokens=count_tokens(repo_context), 151 | model=os.getenv("MODEL"), 152 | embedding=embedding_json, 153 | generated=generated, 154 | created_at=datetime.utcnow().isoformat() # Ensure this is a string 155 | ) 156 | 157 | # Convert the Pydantic model to a dictionary and handle datetime serialization 158 | devcontainer_dict = json.loads(new_devcontainer.json(exclude_unset=True)) 159 | 160 | result = supabase.table("devcontainers").insert(devcontainer_dict).execute() 161 | logging.info(f"Successfully saved to database with devcontainer_url: {devcontainer_url}") 162 | except Exception as e: 163 | logging.error(f"Error while saving to database: {str(e)}") 164 | raise 165 | 166 | return Div( 167 | Article(f"Devcontainer.json {'found in ' + source if source in ['database', 'repository'] else 'generated'}"), 168 | caution_section(), 169 | Pre( 170 | Code(devcontainer_json, id="devcontainer-code", cls="overflow-auto"), 171 | Div( 172 | Button( 173 | Img(cls="w-4 h-4", src="assets/icons/copy-icon.svg", alt="Copy"), 174 | cls="icon-button copy-button", 175 | title="Copy to clipboard", 176 | ), 177 | Button( 178 | Img(cls="w-4 h-4", src="assets/icons/regenerate.svg", alt="Regenerate"), 179 | cls="icon-button regenerate-button", 180 | hx_post=f"/generate?regenerate=true&repo_url={repo_url}", 181 | hx_target="#result", 182 | hx_indicator="#action-text", 183 | title="Regenerate", 184 | ), 185 | Span(cls="action-text", id="action-text"), 186 | cls="button-group" 187 | ), 188 | cls="code-container relative" 189 | ) 190 | ) 191 | except Exception as e: 192 | logging.error(f"An error occurred: {str(e)}", exc_info=True) 193 | return Div(H2("Error"), P(f"An error occurred: {str(e)}")) 194 | 195 | @rt("/manifesto") 196 | async def get(): 197 | return manifesto_page() 198 | 199 | # Serve static files 200 | @rt("/{fname:path}.{ext:static}") 201 | async def get(fname:str, ext:str): 202 | return FileResponse(f'{fname}.{ext}') 203 | 204 | # Initialize clients 205 | if check_env_vars(): 206 | openai_client = setup_azure_openai() 207 | instructor_client = setup_instructor(openai_client) 208 | 209 | if __name__ == "__main__": 210 | logging.info("Starting FastHTML app...") 211 | serve() -------------------------------------------------------------------------------- /migrate.py: -------------------------------------------------------------------------------- 1 | import psycopg2 2 | from dotenv import load_dotenv 3 | import os 4 | import sys 5 | import logging 6 | 7 | # Configure logging 8 | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') 9 | 10 | 11 | # Load environment variables from .env 12 | load_dotenv() 13 | 14 | # Fetch the SUPABASE_DB_URL from environment variables 15 | DATABASE_URL = os.getenv("SUPABASE_DB_URL") 16 | 17 | 18 | # Debugging: Ensure DATABASE_URL is loaded (avoid printing sensitive information) 19 | if DATABASE_URL: 20 | logging.info("SUPABASE_DB_URL loaded successfully.") 21 | else: 22 | logging.error("SUPABASE_DB_URL is not set. Please check your .env file.") 23 | sys.exit(1) # Exit the script with a non-zero status 24 | 25 | # Define the migration SQL 26 | CREATE_TABLE_SQL = """ 27 | CREATE TABLE IF NOT EXISTS devcontainers ( 28 | id INTEGER NOT NULL, 29 | url VARCHAR, 30 | devcontainer_json TEXT, 31 | devcontainer_url VARCHAR, 32 | repo_context TEXT, 33 | tokens INTEGER, 34 | model TEXT, 35 | embedding TEXT, 36 | generated BOOLEAN, 37 | created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), 38 | PRIMARY KEY (id) 39 | ); 40 | """ 41 | 42 | def main(): 43 | connection = None # Initialize connection variable 44 | 45 | try: 46 | # Connect to the PostgreSQL database using the connection string 47 | connection = psycopg2.connect(DATABASE_URL) 48 | connection.autocommit = True # Enable autocommit mode 49 | logging.info("Connection to the database was successful.") 50 | 51 | # Create a cursor to execute SQL queries 52 | cursor = connection.cursor() 53 | 54 | # Execute the CREATE TABLE statement 55 | cursor.execute(CREATE_TABLE_SQL) 56 | logging.info("devcontainers table created (if it didn't exist already).") 57 | 58 | # Optionally, verify the table creation 59 | cursor.execute(""" 60 | SELECT table_name 61 | FROM information_schema.tables 62 | WHERE table_schema = 'public' AND table_type = 'BASE TABLE'; 63 | """) 64 | tables = cursor.fetchall() 65 | logging.info(f"Current tables in 'public' schema: {tables}") 66 | 67 | # Close the cursor 68 | cursor.close() 69 | 70 | except psycopg2.OperationalError as e: 71 | logging.error("OperationalError: Could not connect to the database.") 72 | logging.error("Please check your SUPABASE_DB_URL and ensure the database server is running.") 73 | logging.error(f"Details: {e}") 74 | sys.exit(1) # Exit the script with a non-zero status 75 | 76 | except Exception as e: 77 | logging.error(f"Error running migration: {e}") 78 | sys.exit(1) # Exit the script with a non-zero status 79 | 80 | finally: 81 | if connection: 82 | connection.close() # Close the connection if it was established 83 | logging.info("Database connection closed.") 84 | 85 | if __name__ == "__main__": 86 | main() 87 | -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from datetime import datetime 3 | from typing import Optional 4 | 5 | class DevContainer(BaseModel): 6 | id: Optional[int] = None 7 | url: str 8 | devcontainer_json: str 9 | devcontainer_url: Optional[str] 10 | repo_context: str 11 | tokens: int 12 | model: str 13 | embedding: Optional[str] 14 | generated: bool 15 | created_at: str = datetime.utcnow().isoformat() -------------------------------------------------------------------------------- /prompts/devcontainer.jinja: -------------------------------------------------------------------------------- 1 | Given the following context from a GitHub repository: 2 | 3 | {{ repo_context }} 4 | 5 | {% if existing_devcontainer %} 6 | An existing devcontainer.json file was found in the repository: 7 | 8 | {{ existing_devcontainer }} 9 | 10 | Please use this as a reference and improve upon it, incorporating any new requirements or best practices. 11 | {% endif %} 12 | 13 | Begin by applying Chain of Thought (CoT) reasoning to decompose the context and task into logical, manageable components. Think slowly and pay attention to all important facts in the context such as the ports used by the application and the ports used for testing. 14 | 15 | Generate a devcontainer.json file for this project. The file should include appropriate settings for the development environment based on the project's requirements and structure. The 'features' field is essential and should include a dictionary of features to enable within the container. 16 | 17 | Always add comments (like in the provided example) to explain what each line or block of code does. This will help you and others who come after you understand what each line of code is doing, why it's there and how it works. 18 | 19 | Here's an example of a devcontainer.json: 20 | ```json 21 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 22 | // README at: https://github.com/devcontainers/templates/tree/main/src/python 23 | { 24 | "name": "Python 3 with Streamlit: Groq MoA", 25 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 26 | "image": "mcr.microsoft.com/devcontainers/python:3.12-bookworm", 27 | 28 | "containerEnv": { 29 | "GROQ_API_KEY": "${localEnv:GROQ_API_KEY}" 30 | }, 31 | 32 | // Features to add to the dev container. More info: https://containers.dev/features. 33 | // "features": {}, 34 | 35 | // Configure tool-specific properties. 36 | "customizations": { 37 | // Configure properties specific to VS Code. 38 | "vscode": { 39 | // Set *default* container specific settings.json values on container create. 40 | "settings": { 41 | "python.defaultInterpreterPath": "/usr/local/bin/python", 42 | "python.linting.enabled": true, 43 | "python.linting.pylintEnabled": true, 44 | "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", 45 | "python.formatting.blackPath": "/usr/local/py-utils/bin/black", 46 | "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", 47 | "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", 48 | "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", 49 | "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", 50 | "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", 51 | "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", 52 | "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" 53 | }, 54 | 55 | // Add the IDs of extensions you want installed when the container is created. 56 | "extensions": [ 57 | "streetsidesoftware.code-spell-checker", 58 | "ms-python.python", 59 | "ms-python.vscode-pylance", 60 | "ms-python.isort", 61 | "njpwerner.autodocstring" 62 | ] 63 | } 64 | }, 65 | 66 | // Use 'portsAttributes' to set default properties for specific forwarded ports. 67 | // More info: https://containers.dev/implementors/json_reference/#port-attributes 68 | "portsAttributes": { 69 | "8501": { 70 | "label": "Streamlit - Groq MoA", 71 | "onAutoForward": "openBrowser" 72 | } 73 | }, 74 | 75 | // https://containers.dev/implementors/json_reference/#lifecycle-scripts 76 | "postCreateCommand": "pip3 install -r requirements.txt", 77 | "postAttachCommand": "streamlit run app.py" 78 | 79 | } 80 | ``` 81 | Your goal is to deliver the most logical, secure, efficient, and well-documented devcontainer.json file. -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohappyeyeballs==2.3.5 2 | aiohttp==3.10.2 3 | aiosignal==1.3.1 4 | annotated-types==0.7.0 5 | anyio==4.4.0 6 | appnope==0.1.4 7 | asttokens==2.4.1 8 | async-timeout==4.0.3 9 | attrs==24.2.0 10 | beautifulsoup4==4.12.3 11 | black==24.8.0 12 | certifi==2024.7.4 13 | charset-normalizer==2.1.1 14 | click==8.1.7 15 | cloudscraper==1.2.71 16 | comm==0.2.2 17 | debugpy==1.8.5 18 | decorator==5.1.1 19 | deprecation==2.1.0 20 | distro==1.9.0 21 | docstring_parser==0.16 22 | exceptiongroup==1.2.2 23 | executing==2.0.1 24 | fastcore==1.6.3 25 | fastlite==0.0.7 26 | frozenlist==1.4.1 27 | gitdb==4.0.11 28 | GitPython==3.1.41 29 | gotrue==2.8.0 30 | greenlet==3.0.3 31 | h11==0.14.0 32 | h2==4.1.0 33 | hpack==4.0.0 34 | httpcore==1.0.5 35 | httptools==0.6.1 36 | httpx==0.27.0 37 | hyperframe==6.0.1 38 | idna==3.7 39 | instructor==1.4.0 40 | ipykernel==6.29.5 41 | ipython==8.26.0 42 | isort==5.13.2 43 | itsdangerous==2.2.0 44 | jedi==0.19.1 45 | Jinja2==3.1.4 46 | jiter==0.4.2 47 | jsonschema==4.23.0 48 | jsonschema-specifications==2023.12.1 49 | jupyter_client==8.6.2 50 | jupyter_core==5.7.2 51 | kaleido==0.2.1 52 | markdown-it-py==3.0.0 53 | MarkupSafe==2.1.5 54 | matplotlib-inline==0.1.7 55 | mdurl==0.1.2 56 | multidict==6.0.5 57 | mypy-extensions==1.0.0 58 | nest-asyncio==1.6.0 59 | numpy==2.0.1 60 | oauthlib==3.2.2 61 | openai==1.40.2 62 | packaging==24.1 63 | pandas==2.2.2 64 | parso==0.8.4 65 | pathspec==0.12.1 66 | pexpect==4.9.0 67 | platformdirs==4.2.2 68 | plotly==5.23.0 69 | postgrest==0.16.11 70 | prompt_toolkit==3.0.47 71 | psutil==6.0.0 72 | ptyprocess==0.7.0 73 | pure_eval==0.2.3 74 | pydantic==2.8.2 75 | pydantic_core==2.20.1 76 | Pygments==2.18.0 77 | pyparsing==3.1.2 78 | python-dateutil==2.9.0.post0 79 | python-dotenv==1.0.1 80 | python-fasthtml==0.2.4 81 | python-multipart==0.0.9 82 | pytz==2024.1 83 | PyYAML==6.0.2 84 | pyzmq==26.1.0 85 | realtime==2.0.2 86 | referencing==0.35.1 87 | regex==2024.7.24 88 | replicate==0.31.0 89 | requests==2.32.3 90 | requests-toolbelt==1.0.0 91 | rich==13.7.1 92 | rpds-py==0.20.0 93 | setuptools==69.0.3 94 | shellingham==1.5.4 95 | six==1.16.0 96 | smmap==5.0.1 97 | sniffio==1.3.1 98 | soupsieve==2.5 99 | SQLAlchemy==2.0.32 100 | sqlite-vec==0.1.6 101 | sqlite_minutils==3.36.0.post4 102 | stack-data==0.6.3 103 | starlette==0.38.2 104 | storage3==0.7.7 105 | StrEnum==0.4.15 106 | supabase==2.8.0 107 | supafunc==0.5.1 108 | tenacity==8.5.0 109 | tiktoken==0.7.0 110 | tornado==6.4.1 111 | tqdm==4.66.5 112 | traitlets==5.14.3 113 | typer==0.12.3 114 | typing_extensions==4.12.2 115 | tzdata==2024.1 116 | urllib3==2.2.2 117 | uvicorn==0.30.5 118 | uvloop==0.19.0 119 | watchfiles==0.23.0 120 | wcwidth==0.2.13 121 | websockets==12.0 122 | wheel==0.44.0 123 | yarl==1.9.4 124 | psycopg2-binary==2.9.10 125 | -------------------------------------------------------------------------------- /schemas.py: -------------------------------------------------------------------------------- 1 | # schemas.py 2 | from pydantic import BaseModel, Field 3 | from typing import Optional 4 | 5 | class DevContainerModel(BaseModel): 6 | name: str = Field(description="Name of the dev container") 7 | image: str = Field(description="Docker image to use") 8 | forwardPorts: Optional[list[int]] = Field( 9 | description="Ports to forward from the container to the local machine" 10 | ) 11 | customizations: Optional[dict] = Field( 12 | None, description="Tool-specific configuration" 13 | ) 14 | settings: Optional[dict] = Field( 15 | None, description="VS Code settings to configure the development environment" 16 | ) 17 | postCreateCommand: Optional[str] = Field( 18 | description="Command to run after creating the container" 19 | ) -------------------------------------------------------------------------------- /schemas/devContainer.base.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2019-09/schema", 3 | "description": "Defines a dev container", 4 | "allowComments": true, 5 | "allowTrailingCommas": false, 6 | "definitions": { 7 | "devContainerCommon": { 8 | "type": "object", 9 | "properties": { 10 | "$schema": { 11 | "type": "string", 12 | "format": "uri", 13 | "description": "The JSON schema of the `devcontainer.json` file." 14 | }, 15 | "name": { 16 | "type": "string", 17 | "description": "A name for the dev container which can be displayed to the user." 18 | }, 19 | "features": { 20 | "type": "object", 21 | "description": "Features to add to the dev container.", 22 | "properties": { 23 | "fish": { 24 | "deprecated": true, 25 | "deprecationMessage": "Legacy feature not supported. Please check https://containers.dev/features for replacements." 26 | }, 27 | "maven": { 28 | "deprecated": true, 29 | "deprecationMessage": "Legacy feature will be removed in the future. Please check https://containers.dev/features for replacements. E.g., `ghcr.io/devcontainers/features/java` has an option to install Maven." 30 | }, 31 | "gradle": { 32 | "deprecated": true, 33 | "deprecationMessage": "Legacy feature will be removed in the future. Please check https://containers.dev/features for replacements. E.g., `ghcr.io/devcontainers/features/java` has an option to install Gradle." 34 | }, 35 | "homebrew": { 36 | "deprecated": true, 37 | "deprecationMessage": "Legacy feature not supported. Please check https://containers.dev/features for replacements." 38 | }, 39 | "jupyterlab": { 40 | "deprecated": true, 41 | "deprecationMessage": "Legacy feature will be removed in the future. Please check https://containers.dev/features for replacements. E.g., `ghcr.io/devcontainers/features/python` has an option to install JupyterLab." 42 | } 43 | }, 44 | "additionalProperties": true 45 | }, 46 | "overrideFeatureInstallOrder": { 47 | "type": "array", 48 | "description": "Array consisting of the Feature id (without the semantic version) of Features in the order the user wants them to be installed.", 49 | "items": { 50 | "type": "string" 51 | } 52 | }, 53 | "secrets": { 54 | "type": "object", 55 | "description": "Recommended secrets for this dev container. Recommendations are provided as environment variable keys with optional metadata.", 56 | "patternProperties": { 57 | "^[a-zA-Z_][a-zA-Z0-9_]*$": { 58 | "type": "object", 59 | "description": "Environment variable keys following unix-style naming conventions. eg: ^[a-zA-Z_][a-zA-Z0-9_]*$", 60 | "properties": { 61 | "description": { 62 | "type": "string", 63 | "description": "A description of the secret." 64 | }, 65 | "documentationUrl": { 66 | "type": "string", 67 | "format": "uri", 68 | "description": "A URL to documentation about the secret." 69 | } 70 | }, 71 | "additionalProperties": false 72 | }, 73 | "additionalProperties": false 74 | }, 75 | "additionalProperties": false 76 | }, 77 | "forwardPorts": { 78 | "type": "array", 79 | "description": "Ports that are forwarded from the container to the local machine. Can be an integer port number, or a string of the format \"host:port_number\".", 80 | "items": { 81 | "oneOf": [ 82 | { 83 | "type": "integer", 84 | "maximum": 65535, 85 | "minimum": 0 86 | }, 87 | { 88 | "type": "string", 89 | "pattern": "^([a-z0-9-]+):(\\d{1,5})$" 90 | } 91 | ] 92 | } 93 | }, 94 | "portsAttributes": { 95 | "type": "object", 96 | "patternProperties": { 97 | "(^\\d+(-\\d+)?$)|(.+)": { 98 | "type": "object", 99 | "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", 100 | "properties": { 101 | "onAutoForward": { 102 | "type": "string", 103 | "enum": [ 104 | "notify", 105 | "openBrowser", 106 | "openBrowserOnce", 107 | "openPreview", 108 | "silent", 109 | "ignore" 110 | ], 111 | "enumDescriptions": [ 112 | "Shows a notification when a port is automatically forwarded.", 113 | "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", 114 | "Opens the browser when the port is automatically forwarded, but only the first time the port is forward during a session. Depending on your settings, this could open an embedded browser.", 115 | "Opens a preview in the same window when the port is automatically forwarded.", 116 | "Shows no notification and takes no action when this port is automatically forwarded.", 117 | "This port will not be automatically forwarded." 118 | ], 119 | "description": "Defines the action that occurs when the port is discovered for automatic forwarding", 120 | "default": "notify" 121 | }, 122 | "elevateIfNeeded": { 123 | "type": "boolean", 124 | "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", 125 | "default": false 126 | }, 127 | "label": { 128 | "type": "string", 129 | "description": "Label that will be shown in the UI for this port.", 130 | "default": "Application" 131 | }, 132 | "requireLocalPort": { 133 | "type": "boolean", 134 | "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", 135 | "default": false 136 | }, 137 | "protocol": { 138 | "type": "string", 139 | "enum": [ 140 | "http", 141 | "https" 142 | ], 143 | "description": "The protocol to use when forwarding this port." 144 | } 145 | }, 146 | "default": { 147 | "label": "Application", 148 | "onAutoForward": "notify" 149 | } 150 | } 151 | }, 152 | "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```", 153 | "defaultSnippets": [ 154 | { 155 | "body": { 156 | "${1:3000}": { 157 | "label": "${2:Application}", 158 | "onAutoForward": "notify" 159 | } 160 | } 161 | } 162 | ], 163 | "additionalProperties": false 164 | }, 165 | "otherPortsAttributes": { 166 | "type": "object", 167 | "properties": { 168 | "onAutoForward": { 169 | "type": "string", 170 | "enum": [ 171 | "notify", 172 | "openBrowser", 173 | "openPreview", 174 | "silent", 175 | "ignore" 176 | ], 177 | "enumDescriptions": [ 178 | "Shows a notification when a port is automatically forwarded.", 179 | "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", 180 | "Opens a preview in the same window when the port is automatically forwarded.", 181 | "Shows no notification and takes no action when this port is automatically forwarded.", 182 | "This port will not be automatically forwarded." 183 | ], 184 | "description": "Defines the action that occurs when the port is discovered for automatic forwarding", 185 | "default": "notify" 186 | }, 187 | "elevateIfNeeded": { 188 | "type": "boolean", 189 | "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", 190 | "default": false 191 | }, 192 | "label": { 193 | "type": "string", 194 | "description": "Label that will be shown in the UI for this port.", 195 | "default": "Application" 196 | }, 197 | "requireLocalPort": { 198 | "type": "boolean", 199 | "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", 200 | "default": false 201 | }, 202 | "protocol": { 203 | "type": "string", 204 | "enum": [ 205 | "http", 206 | "https" 207 | ], 208 | "description": "The protocol to use when forwarding this port." 209 | } 210 | }, 211 | "defaultSnippets": [ 212 | { 213 | "body": { 214 | "onAutoForward": "ignore" 215 | } 216 | } 217 | ], 218 | "markdownDescription": "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", 219 | "additionalProperties": false 220 | }, 221 | "updateRemoteUserUID": { 222 | "type": "boolean", 223 | "description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default when opening from a local folder." 224 | }, 225 | "containerEnv": { 226 | "type": "object", 227 | "additionalProperties": { 228 | "type": "string" 229 | }, 230 | "description": "Container environment variables." 231 | }, 232 | "containerUser": { 233 | "type": "string", 234 | "description": "The user the container will be started with. The default is the user on the Docker image." 235 | }, 236 | "mounts": { 237 | "type": "array", 238 | "description": "Mount points to set up when creating the container. See Docker's documentation for the --mount option for the supported syntax.", 239 | "items": { 240 | "anyOf": [ 241 | { 242 | "$ref": "#/definitions/Mount" 243 | }, 244 | { 245 | "type": "string" 246 | } 247 | ] 248 | } 249 | }, 250 | "init": { 251 | "type": "boolean", 252 | "description": "Passes the --init flag when creating the dev container." 253 | }, 254 | "privileged": { 255 | "type": "boolean", 256 | "description": "Passes the --privileged flag when creating the dev container." 257 | }, 258 | "capAdd": { 259 | "type": "array", 260 | "description": "Passes docker capabilities to include when creating the dev container.", 261 | "examples": [ 262 | "SYS_PTRACE" 263 | ], 264 | "items": { 265 | "type": "string" 266 | } 267 | }, 268 | "securityOpt": { 269 | "type": "array", 270 | "description": "Passes docker security options to include when creating the dev container.", 271 | "examples": [ 272 | "seccomp=unconfined" 273 | ], 274 | "items": { 275 | "type": "string" 276 | } 277 | }, 278 | "remoteEnv": { 279 | "type": "object", 280 | "additionalProperties": { 281 | "type": [ 282 | "string", 283 | "null" 284 | ] 285 | }, 286 | "description": "Remote environment variables to set for processes spawned in the container including lifecycle scripts and any remote editor/IDE server process." 287 | }, 288 | "remoteUser": { 289 | "type": "string", 290 | "description": "The username to use for spawning processes in the container including lifecycle scripts and any remote editor/IDE server process. The default is the same user as the container." 291 | }, 292 | "initializeCommand": { 293 | "type": [ 294 | "string", 295 | "array", 296 | "object" 297 | ], 298 | "description": "A command to run locally (i.e Your host machine, cloud VM) before anything else. This command is run before \"onCreateCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", 299 | "items": { 300 | "type": "string" 301 | }, 302 | "additionalProperties": { 303 | "type": [ 304 | "string", 305 | "array" 306 | ], 307 | "items": { 308 | "type": "string" 309 | } 310 | } 311 | }, 312 | "onCreateCommand": { 313 | "type": [ 314 | "string", 315 | "array", 316 | "object" 317 | ], 318 | "description": "A command to run when creating the container. This command is run after \"initializeCommand\" and before \"updateContentCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", 319 | "items": { 320 | "type": "string" 321 | }, 322 | "additionalProperties": { 323 | "type": [ 324 | "string", 325 | "array" 326 | ], 327 | "items": { 328 | "type": "string" 329 | } 330 | } 331 | }, 332 | "updateContentCommand": { 333 | "type": [ 334 | "string", 335 | "array", 336 | "object" 337 | ], 338 | "description": "A command to run when creating the container and rerun when the workspace content was updated while creating the container. This command is run after \"onCreateCommand\" and before \"postCreateCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", 339 | "items": { 340 | "type": "string" 341 | }, 342 | "additionalProperties": { 343 | "type": [ 344 | "string", 345 | "array" 346 | ], 347 | "items": { 348 | "type": "string" 349 | } 350 | } 351 | }, 352 | "postCreateCommand": { 353 | "type": [ 354 | "string", 355 | "array", 356 | "object" 357 | ], 358 | "description": "A command to run after creating the container. This command is run after \"updateContentCommand\" and before \"postStartCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", 359 | "items": { 360 | "type": "string" 361 | }, 362 | "additionalProperties": { 363 | "type": [ 364 | "string", 365 | "array" 366 | ], 367 | "items": { 368 | "type": "string" 369 | } 370 | } 371 | }, 372 | "postStartCommand": { 373 | "type": [ 374 | "string", 375 | "array", 376 | "object" 377 | ], 378 | "description": "A command to run after starting the container. This command is run after \"postCreateCommand\" and before \"postAttachCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", 379 | "items": { 380 | "type": "string" 381 | }, 382 | "additionalProperties": { 383 | "type": [ 384 | "string", 385 | "array" 386 | ], 387 | "items": { 388 | "type": "string" 389 | } 390 | } 391 | }, 392 | "postAttachCommand": { 393 | "type": [ 394 | "string", 395 | "array", 396 | "object" 397 | ], 398 | "description": "A command to run when attaching to the container. This command is run after \"postStartCommand\". If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell. If this is an object, each provided command will be run in parallel.", 399 | "items": { 400 | "type": "string" 401 | }, 402 | "additionalProperties": { 403 | "type": [ 404 | "string", 405 | "array" 406 | ], 407 | "items": { 408 | "type": "string" 409 | } 410 | } 411 | }, 412 | "waitFor": { 413 | "type": "string", 414 | "enum": [ 415 | "initializeCommand", 416 | "onCreateCommand", 417 | "updateContentCommand", 418 | "postCreateCommand", 419 | "postStartCommand" 420 | ], 421 | "description": "The user command to wait for before continuing execution in the background while the UI is starting up. The default is \"updateContentCommand\"." 422 | }, 423 | "userEnvProbe": { 424 | "type": "string", 425 | "enum": [ 426 | "none", 427 | "loginShell", 428 | "loginInteractiveShell", 429 | "interactiveShell" 430 | ], 431 | "description": "User environment probe to run. The default is \"loginInteractiveShell\"." 432 | }, 433 | "hostRequirements": { 434 | "type": "object", 435 | "description": "Host hardware requirements.", 436 | "properties": { 437 | "cpus": { 438 | "type": "integer", 439 | "minimum": 1, 440 | "description": "Number of required CPUs." 441 | }, 442 | "memory": { 443 | "type": "string", 444 | "pattern": "^\\d+([tgmk]b)?$", 445 | "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." 446 | }, 447 | "storage": { 448 | "type": "string", 449 | "pattern": "^\\d+([tgmk]b)?$", 450 | "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." 451 | }, 452 | "gpu": { 453 | "oneOf": [ 454 | { 455 | "type": [ 456 | "boolean", 457 | "string" 458 | ], 459 | "enum": [ 460 | true, 461 | false, 462 | "optional" 463 | ], 464 | "description": "Indicates whether a GPU is required. The string \"optional\" indicates that a GPU is optional. An object value can be used to configure more detailed requirements." 465 | }, 466 | { 467 | "type": "object", 468 | "properties": { 469 | "cores": { 470 | "type": "integer", 471 | "minimum": 1, 472 | "description": "Number of required cores." 473 | }, 474 | "memory": { 475 | "type": "string", 476 | "pattern": "^\\d+([tgmk]b)?$", 477 | "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." 478 | } 479 | }, 480 | "description": "Indicates whether a GPU is required. The string \"optional\" indicates that a GPU is optional. An object value can be used to configure more detailed requirements.", 481 | "additionalProperties": false 482 | } 483 | ] 484 | } 485 | }, 486 | "unevaluatedProperties": false 487 | }, 488 | "customizations": { 489 | "type": "object", 490 | "description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations." 491 | }, 492 | "additionalProperties": { 493 | "type": "object", 494 | "additionalProperties": true 495 | } 496 | } 497 | }, 498 | "nonComposeBase": { 499 | "type": "object", 500 | "properties": { 501 | "appPort": { 502 | "type": [ 503 | "integer", 504 | "string", 505 | "array" 506 | ], 507 | "description": "Application ports that are exposed by the container. This can be a single port or an array of ports. Each port can be a number or a string. A number is mapped to the same port on the host. A string is passed to Docker unchanged and can be used to map ports differently, e.g. \"8000:8010\".", 508 | "items": { 509 | "type": [ 510 | "integer", 511 | "string" 512 | ] 513 | } 514 | }, 515 | "runArgs": { 516 | "type": "array", 517 | "description": "The arguments required when starting in the container.", 518 | "items": { 519 | "type": "string" 520 | } 521 | }, 522 | "shutdownAction": { 523 | "type": "string", 524 | "enum": [ 525 | "none", 526 | "stopContainer" 527 | ], 528 | "description": "Action to take when the user disconnects from the container in their editor. The default is to stop the container." 529 | }, 530 | "overrideCommand": { 531 | "type": "boolean", 532 | "description": "Whether to overwrite the command specified in the image. The default is true." 533 | }, 534 | "workspaceFolder": { 535 | "type": "string", 536 | "description": "The path of the workspace folder inside the container." 537 | }, 538 | "workspaceMount": { 539 | "type": "string", 540 | "description": "The --mount parameter for docker run. The default is to mount the project folder at /workspaces/$project." 541 | } 542 | } 543 | }, 544 | "dockerfileContainer": { 545 | "oneOf": [ 546 | { 547 | "type": "object", 548 | "properties": { 549 | "build": { 550 | "type": "object", 551 | "description": "Docker build-related options.", 552 | "allOf": [ 553 | { 554 | "type": "object", 555 | "properties": { 556 | "dockerfile": { 557 | "type": "string", 558 | "description": "The location of the Dockerfile that defines the contents of the container. The path is relative to the folder containing the `devcontainer.json` file." 559 | }, 560 | "context": { 561 | "type": "string", 562 | "description": "The location of the context folder for building the Docker image. The path is relative to the folder containing the `devcontainer.json` file." 563 | } 564 | }, 565 | "required": [ 566 | "dockerfile" 567 | ] 568 | }, 569 | { 570 | "$ref": "#/definitions/buildOptions" 571 | } 572 | ], 573 | "unevaluatedProperties": false 574 | } 575 | }, 576 | "required": [ 577 | "build" 578 | ] 579 | }, 580 | { 581 | "allOf": [ 582 | { 583 | "type": "object", 584 | "properties": { 585 | "dockerFile": { 586 | "type": "string", 587 | "description": "The location of the Dockerfile that defines the contents of the container. The path is relative to the folder containing the `devcontainer.json` file." 588 | }, 589 | "context": { 590 | "type": "string", 591 | "description": "The location of the context folder for building the Docker image. The path is relative to the folder containing the `devcontainer.json` file." 592 | } 593 | }, 594 | "required": [ 595 | "dockerFile" 596 | ] 597 | }, 598 | { 599 | "type": "object", 600 | "properties": { 601 | "build": { 602 | "description": "Docker build-related options.", 603 | "$ref": "#/definitions/buildOptions" 604 | } 605 | } 606 | } 607 | ] 608 | } 609 | ] 610 | }, 611 | "buildOptions": { 612 | "type": "object", 613 | "properties": { 614 | "target": { 615 | "type": "string", 616 | "description": "Target stage in a multi-stage build." 617 | }, 618 | "args": { 619 | "type": "object", 620 | "additionalProperties": { 621 | "type": [ 622 | "string" 623 | ] 624 | }, 625 | "description": "Build arguments." 626 | }, 627 | "cacheFrom": { 628 | "type": [ 629 | "string", 630 | "array" 631 | ], 632 | "description": "The image to consider as a cache. Use an array to specify multiple images.", 633 | "items": { 634 | "type": "string" 635 | } 636 | }, 637 | "options": { 638 | "type": "array", 639 | "description": "Additional arguments passed to the build command.", 640 | "items": { 641 | "type": "string" 642 | } 643 | } 644 | } 645 | }, 646 | "imageContainer": { 647 | "type": "object", 648 | "properties": { 649 | "image": { 650 | "type": "string", 651 | "description": "The docker image that will be used to create the container." 652 | } 653 | }, 654 | "required": [ 655 | "image" 656 | ] 657 | }, 658 | "composeContainer": { 659 | "type": "object", 660 | "properties": { 661 | "dockerComposeFile": { 662 | "type": [ 663 | "string", 664 | "array" 665 | ], 666 | "description": "The name of the docker-compose file(s) used to start the services.", 667 | "items": { 668 | "type": "string" 669 | } 670 | }, 671 | "service": { 672 | "type": "string", 673 | "description": "The service you want to work on. This is considered the primary container for your dev environment which your editor will connect to." 674 | }, 675 | "runServices": { 676 | "type": "array", 677 | "description": "An array of services that should be started and stopped.", 678 | "items": { 679 | "type": "string" 680 | } 681 | }, 682 | "workspaceFolder": { 683 | "type": "string", 684 | "description": "The path of the workspace folder inside the container. This is typically the target path of a volume mount in the docker-compose.yml." 685 | }, 686 | "shutdownAction": { 687 | "type": "string", 688 | "enum": [ 689 | "none", 690 | "stopCompose" 691 | ], 692 | "description": "Action to take when the user disconnects from the primary container in their editor. The default is to stop all of the compose containers." 693 | }, 694 | "overrideCommand": { 695 | "type": "boolean", 696 | "description": "Whether to overwrite the command specified in the image. The default is false." 697 | } 698 | }, 699 | "required": [ 700 | "dockerComposeFile", 701 | "service", 702 | "workspaceFolder" 703 | ] 704 | }, 705 | "Mount": { 706 | "type": "object", 707 | "properties": { 708 | "type": { 709 | "type": "string", 710 | "enum": [ 711 | "bind", 712 | "volume" 713 | ], 714 | "description": "Mount type." 715 | }, 716 | "source": { 717 | "type": "string", 718 | "description": "Mount source." 719 | }, 720 | "target": { 721 | "type": "string", 722 | "description": "Mount target." 723 | } 724 | }, 725 | "required": [ 726 | "type", 727 | "target" 728 | ], 729 | "additionalProperties": false 730 | } 731 | }, 732 | "oneOf": [ 733 | { 734 | "allOf": [ 735 | { 736 | "oneOf": [ 737 | { 738 | "allOf": [ 739 | { 740 | "oneOf": [ 741 | { 742 | "$ref": "#/definitions/dockerfileContainer" 743 | }, 744 | { 745 | "$ref": "#/definitions/imageContainer" 746 | } 747 | ] 748 | }, 749 | { 750 | "$ref": "#/definitions/nonComposeBase" 751 | } 752 | ] 753 | }, 754 | { 755 | "$ref": "#/definitions/composeContainer" 756 | } 757 | ] 758 | }, 759 | { 760 | "$ref": "#/definitions/devContainerCommon" 761 | } 762 | ] 763 | }, 764 | { 765 | "type": "object", 766 | "$ref": "#/definitions/devContainerCommon", 767 | "additionalProperties": false 768 | } 769 | ], 770 | "unevaluatedProperties": false 771 | } 772 | -------------------------------------------------------------------------------- /supabase_client.py: -------------------------------------------------------------------------------- 1 | import os 2 | from supabase import create_client, Client 3 | from dotenv import load_dotenv 4 | 5 | load_dotenv() 6 | 7 | url: str = os.environ.get("SUPABASE_URL") 8 | key: str = os.environ.get("SUPABASE_KEY") 9 | supabase: Client = create_client(url, key) -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["*.py"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | } 9 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | from dotenv import load_dotenv 4 | 5 | def main(): 6 | # Unset any existing GITHUB_TOKEN environment variable 7 | if 'GITHUB_TOKEN' in os.environ: 8 | del os.environ['GITHUB_TOKEN'] 9 | 10 | # Load environment variables from .env file 11 | load_dotenv() 12 | 13 | token = os.getenv("GITHUB_TOKEN") 14 | if not token: 15 | print("GITHUB_TOKEN environment variable is not set.") 16 | return 17 | 18 | # Print all environment variables starting with GITHUB to debug 19 | print("Environment Variables:") 20 | for key, value in os.environ.items(): 21 | if 'GITHUB' in key: 22 | print(f"{key}: {value[:5]}...") 23 | 24 | contents_api_url = "https://api.github.com/repos/ckeditor/ckeditor5/contents" 25 | 26 | headers = { 27 | "Accept": "application/vnd.github.v3+json", 28 | "Authorization": f"token {token}", 29 | } 30 | 31 | print(f"GITHUB_TOKEN: {token[:5]}...") 32 | print(f"Headers: {headers}") 33 | print(f"Contents API URL: {contents_api_url}") 34 | 35 | response = requests.get(contents_api_url, headers=headers) 36 | print(f"Response (standard): {response.status_code} - {response.text}") 37 | 38 | http_proxy = os.getenv('HTTP_PROXY', '') 39 | https_proxy = os.getenv('HTTPS_PROXY', '') 40 | print(f"HTTP_PROXY: {http_proxy}") 41 | print(f"HTTPS_PROXY: {https_proxy}") 42 | 43 | # Try turning off SSL verify just to debug if it's causing issues 44 | response_no_ssl = requests.get(contents_api_url, headers=headers, verify=False) 45 | print(f"Response (no SSL verify): {response_no_ssl.status_code} - {response_no_ssl.text}") 46 | 47 | if __name__ == "__main__": 48 | main() --------------------------------------------------------------------------------