├── .env.example ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── prompt.ts ├── scripts.ts ├── src ├── helpers │ ├── cli.ts │ ├── container.ts │ ├── corrections.ts │ ├── github.ts │ └── openai.ts ├── index.ts └── lib │ ├── build.ts │ └── buildPlan.ts └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | # Name and email that will be used for git commits. 2 | GIT_AUTHOR_NAME=Gitwit 3 | GIT_AUTHOR_EMAIL=git@gitwit.dev 4 | 5 | # Username and personal access token for accessing GitHub. 6 | # Get a token from here: https://github.com/settings/tokens 7 | GITHUB_USERNAME=gitwitdev 8 | GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 9 | 10 | # ChatGPT API key 11 | # Get a secret key from here: https://platform.openai.com/account/api-keys 12 | OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | /build/ 3 | /dist/ 4 | 5 | .vscode/ 6 | node_modules/ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 James Murdza 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitWit Agent 2 | 3 | 4 | 5 | GitWit is a container-based agent specialized in making useful commits to git repositories. Given a description (i.e. "implement dark mode") it either checks out a repository or creates a new one, makes these changes, and then pushes the changes to master. (Skip to [How it works](#how-it-works).) 6 | 7 | Given there exist [a few agents](https://github.com/jamesmurdza/awesome-ai-devtools#pr-agents) with a similar purpose—**why is GitWit different?** GitWit interacts with the filesystem in a temporary sandbox and thus can run any shell command. It _writes code that writes code_. This makes it very flexible and repurposable for a number of interesting use cases. 8 | 9 | This agent is also live for testing at [app.gitwit.dev](https://app.gitwit.dev) and has generated over 1000 repositories! 10 | 11 | ### Contents 12 | - [How to run it](#how-to-run-it) 13 | - [Commands](#commands) 14 | - [Examples](#examples) 15 | - [Demos](#demos) 16 | - [Additional configuration](#additional-configuration) 17 | - [LLM configuration](#llm-configuration) 18 | - [How it works](#how-it-works) 19 | 20 | ## How to run it 21 | 22 | Before you start: 23 | 1. You need NodeJS (v18). 24 | 2. You need Docker. 25 | 3. The agent will access to your GitHub account via [personal access token](https://github.com/settings/tokens). 26 | 4. You need an [OpenAI API key](https://platform.openai.com/account/api-keys). 27 | 28 | Setup: 29 | 1. `git clone https://github.com/jamesmurdza/gitwit && cd gitwit` to clone this repository. 30 | 2. `cp .env.example .env` to create a .env file. Update **GITHUB_USERNAME**, **GITHUB_TOKEN** and **OPENAI_API_KEY** with your values. 31 | 3. Start Docker! (GitWit creates a temporary Docker container for each run.) The easiest way to do this locally is with Docker Desktop. See here to connect to a remote docker server. 32 | 4. `docker pull node:latest` to download the base Docker image. 33 | 5. `run npm install` to install dependencies. 34 | 35 | You are ready to go! 36 | 37 | ## Commands 38 | 39 | Generate a new GitHub repository: 40 | 41 | `npm run start` 42 | 43 | Generate a repository with the same name and description as the last run: 44 | 45 | `npm run start -- --again` 46 | 47 | Generate a repository with the same name, description, and build script as the last run: 48 | 49 | `npm run start -- --offline` 50 | 51 | Debug the build script from the last run: 52 | 53 | `npm run start -- --offline --debug` 54 | 55 | Generate a new branch on an existing repository: 56 | 57 | `npm run start -- --branch` 58 | 59 | Generate a new branch with the same name and description as the last run: 60 | 61 | `npm run start -- --branch --again` 62 | 63 | ## Examples 64 | 65 | Articles and tutorials: 66 | 67 | - [Building a Chrome Extension from Scratch using GitWit](https://codesphere.com/articles/building-a-chrome-extension-using-gitwit) 68 | 69 | Examples of entire repositories generated with GitWit: 70 | 71 | - [gitwitapp/doodle-app](https://github.com/gitwitapp/doodle-app): HTML/JS drawing app. 72 | - [gitwitapp/cached-http-proxy-server](https://github.com/gitwitapp/cached-http-proxy-server): NodeJS proxy server with caching. 73 | - [gitwitapp/reddit-news-viewer](https://github.com/gitwitapp/reddit-news-viewer): Python script for scraping Reddit headlines. 74 | - [gitwitapp/python-discord-chatbot](https://github.com/gitwitapp/python-discord-chatbot): Simple Discord bot written in Python. 75 | - [gitwitapp/live-BTC-ticker](https://github.com/gitwitapp/live-BTC-ticker): ReactJS app using d3.js to chart BTC prices. 76 | - [gitwitapp/web-calculator](https://github.com/gitwitapp/web-calculator): Simple HTML/JS calculator. 77 | - [gitwitapp/customer-oop-demo](https://github.com/gitwitapp/customer-oop-demo): Example of generated unit tests. 78 | 79 | ## Demos 80 | 81 | The agent has two modes: 82 | - Create new **repository**: Given a prompt and a repository name, spawn the repository 83 | 84 | https://github.com/gitwitdev/gitwitdev.github.io/assets/33395784/55537249-c301-4e13-84e5-0cdb06174071 85 | 86 | - Create new **branch**: Given a prompt, an existing repository and a branch name, spawn the new branch 87 | 88 | https://github.com/gitwitdev/gitwitdev.github.io/assets/33395784/9315a17c-fc72-431a-a648-16ba42938faa 89 | 90 | ## Additional configuration 91 | 92 | To add new repositories to a GitHub organization: 93 | ```sh 94 | GITHUB_ORGNAME=mygithuborg 95 | ``` 96 | 97 | To use a remote Docker server: 98 | ```sh 99 | DOCKER_API_HOST=1.2.3.4 100 | DOCKER_API_PORT=2375 101 | DOCKER_API_KEY=-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY----- 102 | DOCKER_API_CA=-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE----- 103 | DOCKER_API_CERT=-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE----- 104 | ``` 105 | 106 | To enable logging or caching with Helicone: 107 | ```sh 108 | # Required: 109 | OPENAI_BASE_URL=https://oai.hconeai.com/v1 110 | HELICONE_API_KEY=sk-xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxx 111 | # Optional: 112 | # OPENAI_CACHE_ENABLED=true 113 | ``` 114 | 115 | ## LLM configuration 116 | 117 | By default, GitWit Agent is set to use the OpenAI API with gpt-3.5-turbo and a temperature setting of 0.2. These settings can be configured in [index.js](https://github.com/jamesmurdza/gitwit-agent/blob/main/index.ts). 118 | 119 | GitWit can also be used with LangChain to compose any LangChain supported [chat model](https://js.langchain.com/docs/modules/model_io/models/chat/). An example of this is in [llm.js](https://github.com/jamesmurdza/gitwit-agent/blob/langchain/llm.ts) on the [langchain](https://github.com/jamesmurdza/gitwit-agent/tree/langchain) branch. 120 | 121 | ## How it works 122 | 123 | 124 | 125 | 145 | 149 | 150 | 151 | 162 | 166 | 167 |
126 |

127 | This shows the various APIs and connections in the program. 128 |

129 |

130 | Code generator: (index.ts) This is the central component that contains the logic necessary to create a new repository or branch. 131 |

132 |

133 | OpenAI API: (openai.ts) This is a wrapper functions I wrote around the OpenAI chat completion API. 134 |

135 |

136 | GitHub API: (github.ts) This is a collection of wrapper functions I wrote around the GitHub API. 137 |

138 |

139 | Docker/Container: (container.ts) This is a collection of wrapper functions I wrote around dockerode to simplify interacting with a Docker server 140 |

141 |

142 | Git Repository: (scripts.ts) This is a collection of shell scripts that are injected into the container in order to perform basic git operations. 143 |

144 |
146 | GitWit Architecture 147 |

Overview of the system and its parts

148 |
152 |

153 | This diagram shows a sequential breakdown of the steps in index.ts. A user prompt is used to generate a plan, which is then used to generate a shell script which is run in the container. 154 |

155 |

156 | Note: This diagram is for the "branch creation" mode. The equivalent diagram for "repository generation" mode would have "Create a new repo" for Step 1, and Step 2 would be removed. That's because the main purpose of the plan is to selectively decide which files to inject in the context of the final LLM call. 157 |

158 |

159 | We select entire files that should or should not be included in the context of the final LLM call, a simple implementation of retrieval-augmented generation! 160 |

161 |
163 | GitWit Agent 164 |

Overview of the agentic process

165 |
168 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitwit", 3 | "version": "0.1.7", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "gitwit", 9 | "version": "0.1.7", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dockerode": "^3.3.5", 13 | "dotenv": "^16.0.3", 14 | "fs": "^0.0.1-security", 15 | "json5": "^2.2.3", 16 | "node-fetch": "^2.6.9", 17 | "openai": "^3.2.1", 18 | "tar": "^6.1.13" 19 | }, 20 | "devDependencies": { 21 | "@types/dockerode": "^3.3.16", 22 | "@types/node": "^18.15.11", 23 | "@types/node-fetch": "^2.6.3", 24 | "@types/tar": "^6.1.4" 25 | } 26 | }, 27 | "node_modules/@balena/dockerignore": { 28 | "version": "1.0.2", 29 | "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", 30 | "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==" 31 | }, 32 | "node_modules/@types/docker-modem": { 33 | "version": "3.0.2", 34 | "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.2.tgz", 35 | "integrity": "sha512-qC7prjoEYR2QEe6SmCVfB1x3rfcQtUr1n4x89+3e0wSTMQ/KYCyf+/RAA9n2tllkkNc6//JMUZePdFRiGIWfaQ==", 36 | "dev": true, 37 | "dependencies": { 38 | "@types/node": "*", 39 | "@types/ssh2": "*" 40 | } 41 | }, 42 | "node_modules/@types/dockerode": { 43 | "version": "3.3.16", 44 | "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.16.tgz", 45 | "integrity": "sha512-ZAX2VrkTjwjk1808T8m6vMr+CFXSLiDD+tkEkLThI+v83AfzlYQZEWfZKwFyk1PWopSXkdDunmIhrF7sxt+zWg==", 46 | "dev": true, 47 | "dependencies": { 48 | "@types/docker-modem": "*", 49 | "@types/node": "*" 50 | } 51 | }, 52 | "node_modules/@types/node": { 53 | "version": "18.15.11", 54 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", 55 | "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", 56 | "dev": true 57 | }, 58 | "node_modules/@types/node-fetch": { 59 | "version": "2.6.3", 60 | "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.3.tgz", 61 | "integrity": "sha512-ETTL1mOEdq/sxUtgtOhKjyB2Irra4cjxksvcMUR5Zr4n+PxVhsCD9WS46oPbHL3et9Zde7CNRr+WUNlcHvsX+w==", 62 | "dev": true, 63 | "dependencies": { 64 | "@types/node": "*", 65 | "form-data": "^3.0.0" 66 | } 67 | }, 68 | "node_modules/@types/node-fetch/node_modules/form-data": { 69 | "version": "3.0.1", 70 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", 71 | "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", 72 | "dev": true, 73 | "dependencies": { 74 | "asynckit": "^0.4.0", 75 | "combined-stream": "^1.0.8", 76 | "mime-types": "^2.1.12" 77 | }, 78 | "engines": { 79 | "node": ">= 6" 80 | } 81 | }, 82 | "node_modules/@types/ssh2": { 83 | "version": "1.11.8", 84 | "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.11.8.tgz", 85 | "integrity": "sha512-BsD9yrKmD8avjbR+N5tvv0jxYHzizcrC156YkPbNjqbu81tCm4ZdS7D6KtXbZfz+CFHgFrTC7j046Lr39W5eig==", 86 | "dev": true, 87 | "dependencies": { 88 | "@types/node": "^18.11.18" 89 | } 90 | }, 91 | "node_modules/@types/tar": { 92 | "version": "6.1.4", 93 | "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.4.tgz", 94 | "integrity": "sha512-Cp4oxpfIzWt7mr2pbhHT2OTXGMAL0szYCzuf8lRWyIMCgsx6/Hfc3ubztuhvzXHXgraTQxyOCmmg7TDGIMIJJQ==", 95 | "dev": true, 96 | "dependencies": { 97 | "@types/node": "*", 98 | "minipass": "^4.0.0" 99 | } 100 | }, 101 | "node_modules/asn1": { 102 | "version": "0.2.6", 103 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", 104 | "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", 105 | "dependencies": { 106 | "safer-buffer": "~2.1.0" 107 | } 108 | }, 109 | "node_modules/asynckit": { 110 | "version": "0.4.0", 111 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 112 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 113 | }, 114 | "node_modules/axios": { 115 | "version": "0.26.1", 116 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", 117 | "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", 118 | "dependencies": { 119 | "follow-redirects": "^1.14.8" 120 | } 121 | }, 122 | "node_modules/base64-js": { 123 | "version": "1.5.1", 124 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 125 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 126 | "funding": [ 127 | { 128 | "type": "github", 129 | "url": "https://github.com/sponsors/feross" 130 | }, 131 | { 132 | "type": "patreon", 133 | "url": "https://www.patreon.com/feross" 134 | }, 135 | { 136 | "type": "consulting", 137 | "url": "https://feross.org/support" 138 | } 139 | ] 140 | }, 141 | "node_modules/bcrypt-pbkdf": { 142 | "version": "1.0.2", 143 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 144 | "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", 145 | "dependencies": { 146 | "tweetnacl": "^0.14.3" 147 | } 148 | }, 149 | "node_modules/bl": { 150 | "version": "4.1.0", 151 | "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", 152 | "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", 153 | "dependencies": { 154 | "buffer": "^5.5.0", 155 | "inherits": "^2.0.4", 156 | "readable-stream": "^3.4.0" 157 | } 158 | }, 159 | "node_modules/buffer": { 160 | "version": "5.7.1", 161 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 162 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 163 | "funding": [ 164 | { 165 | "type": "github", 166 | "url": "https://github.com/sponsors/feross" 167 | }, 168 | { 169 | "type": "patreon", 170 | "url": "https://www.patreon.com/feross" 171 | }, 172 | { 173 | "type": "consulting", 174 | "url": "https://feross.org/support" 175 | } 176 | ], 177 | "dependencies": { 178 | "base64-js": "^1.3.1", 179 | "ieee754": "^1.1.13" 180 | } 181 | }, 182 | "node_modules/buildcheck": { 183 | "version": "0.0.3", 184 | "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.3.tgz", 185 | "integrity": "sha512-pziaA+p/wdVImfcbsZLNF32EiWyujlQLwolMqUQE8xpKNOH7KmZQaY8sXN7DGOEzPAElo9QTaeNRfGnf3iOJbA==", 186 | "optional": true, 187 | "engines": { 188 | "node": ">=10.0.0" 189 | } 190 | }, 191 | "node_modules/chownr": { 192 | "version": "2.0.0", 193 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", 194 | "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", 195 | "engines": { 196 | "node": ">=10" 197 | } 198 | }, 199 | "node_modules/combined-stream": { 200 | "version": "1.0.8", 201 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 202 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 203 | "dependencies": { 204 | "delayed-stream": "~1.0.0" 205 | }, 206 | "engines": { 207 | "node": ">= 0.8" 208 | } 209 | }, 210 | "node_modules/cpu-features": { 211 | "version": "0.0.4", 212 | "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.4.tgz", 213 | "integrity": "sha512-fKiZ/zp1mUwQbnzb9IghXtHtDoTMtNeb8oYGx6kX2SYfhnG0HNdBEBIzB9b5KlXu5DQPhfy3mInbBxFcgwAr3A==", 214 | "hasInstallScript": true, 215 | "optional": true, 216 | "dependencies": { 217 | "buildcheck": "0.0.3", 218 | "nan": "^2.15.0" 219 | }, 220 | "engines": { 221 | "node": ">=10.0.0" 222 | } 223 | }, 224 | "node_modules/debug": { 225 | "version": "4.3.4", 226 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 227 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 228 | "dependencies": { 229 | "ms": "2.1.2" 230 | }, 231 | "engines": { 232 | "node": ">=6.0" 233 | }, 234 | "peerDependenciesMeta": { 235 | "supports-color": { 236 | "optional": true 237 | } 238 | } 239 | }, 240 | "node_modules/delayed-stream": { 241 | "version": "1.0.0", 242 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 243 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 244 | "engines": { 245 | "node": ">=0.4.0" 246 | } 247 | }, 248 | "node_modules/docker-modem": { 249 | "version": "3.0.8", 250 | "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.8.tgz", 251 | "integrity": "sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==", 252 | "dependencies": { 253 | "debug": "^4.1.1", 254 | "readable-stream": "^3.5.0", 255 | "split-ca": "^1.0.1", 256 | "ssh2": "^1.11.0" 257 | }, 258 | "engines": { 259 | "node": ">= 8.0" 260 | } 261 | }, 262 | "node_modules/dockerode": { 263 | "version": "3.3.5", 264 | "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.3.5.tgz", 265 | "integrity": "sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==", 266 | "dependencies": { 267 | "@balena/dockerignore": "^1.0.2", 268 | "docker-modem": "^3.0.0", 269 | "tar-fs": "~2.0.1" 270 | }, 271 | "engines": { 272 | "node": ">= 8.0" 273 | } 274 | }, 275 | "node_modules/dotenv": { 276 | "version": "16.0.3", 277 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", 278 | "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", 279 | "engines": { 280 | "node": ">=12" 281 | } 282 | }, 283 | "node_modules/end-of-stream": { 284 | "version": "1.4.4", 285 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 286 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 287 | "dependencies": { 288 | "once": "^1.4.0" 289 | } 290 | }, 291 | "node_modules/follow-redirects": { 292 | "version": "1.15.2", 293 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", 294 | "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", 295 | "funding": [ 296 | { 297 | "type": "individual", 298 | "url": "https://github.com/sponsors/RubenVerborgh" 299 | } 300 | ], 301 | "engines": { 302 | "node": ">=4.0" 303 | }, 304 | "peerDependenciesMeta": { 305 | "debug": { 306 | "optional": true 307 | } 308 | } 309 | }, 310 | "node_modules/form-data": { 311 | "version": "4.0.0", 312 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 313 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 314 | "dependencies": { 315 | "asynckit": "^0.4.0", 316 | "combined-stream": "^1.0.8", 317 | "mime-types": "^2.1.12" 318 | }, 319 | "engines": { 320 | "node": ">= 6" 321 | } 322 | }, 323 | "node_modules/fs": { 324 | "version": "0.0.1-security", 325 | "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", 326 | "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" 327 | }, 328 | "node_modules/fs-constants": { 329 | "version": "1.0.0", 330 | "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", 331 | "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" 332 | }, 333 | "node_modules/fs-minipass": { 334 | "version": "2.1.0", 335 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", 336 | "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", 337 | "dependencies": { 338 | "minipass": "^3.0.0" 339 | }, 340 | "engines": { 341 | "node": ">= 8" 342 | } 343 | }, 344 | "node_modules/fs-minipass/node_modules/minipass": { 345 | "version": "3.3.6", 346 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", 347 | "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", 348 | "dependencies": { 349 | "yallist": "^4.0.0" 350 | }, 351 | "engines": { 352 | "node": ">=8" 353 | } 354 | }, 355 | "node_modules/ieee754": { 356 | "version": "1.2.1", 357 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 358 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 359 | "funding": [ 360 | { 361 | "type": "github", 362 | "url": "https://github.com/sponsors/feross" 363 | }, 364 | { 365 | "type": "patreon", 366 | "url": "https://www.patreon.com/feross" 367 | }, 368 | { 369 | "type": "consulting", 370 | "url": "https://feross.org/support" 371 | } 372 | ] 373 | }, 374 | "node_modules/inherits": { 375 | "version": "2.0.4", 376 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 377 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 378 | }, 379 | "node_modules/json5": { 380 | "version": "2.2.3", 381 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 382 | "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", 383 | "bin": { 384 | "json5": "lib/cli.js" 385 | }, 386 | "engines": { 387 | "node": ">=6" 388 | } 389 | }, 390 | "node_modules/mime-db": { 391 | "version": "1.52.0", 392 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 393 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 394 | "engines": { 395 | "node": ">= 0.6" 396 | } 397 | }, 398 | "node_modules/mime-types": { 399 | "version": "2.1.35", 400 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 401 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 402 | "dependencies": { 403 | "mime-db": "1.52.0" 404 | }, 405 | "engines": { 406 | "node": ">= 0.6" 407 | } 408 | }, 409 | "node_modules/minipass": { 410 | "version": "4.2.5", 411 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", 412 | "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==", 413 | "engines": { 414 | "node": ">=8" 415 | } 416 | }, 417 | "node_modules/minizlib": { 418 | "version": "2.1.2", 419 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", 420 | "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", 421 | "dependencies": { 422 | "minipass": "^3.0.0", 423 | "yallist": "^4.0.0" 424 | }, 425 | "engines": { 426 | "node": ">= 8" 427 | } 428 | }, 429 | "node_modules/minizlib/node_modules/minipass": { 430 | "version": "3.3.6", 431 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", 432 | "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", 433 | "dependencies": { 434 | "yallist": "^4.0.0" 435 | }, 436 | "engines": { 437 | "node": ">=8" 438 | } 439 | }, 440 | "node_modules/mkdirp": { 441 | "version": "1.0.4", 442 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 443 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", 444 | "bin": { 445 | "mkdirp": "bin/cmd.js" 446 | }, 447 | "engines": { 448 | "node": ">=10" 449 | } 450 | }, 451 | "node_modules/mkdirp-classic": { 452 | "version": "0.5.3", 453 | "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", 454 | "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" 455 | }, 456 | "node_modules/ms": { 457 | "version": "2.1.2", 458 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 459 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 460 | }, 461 | "node_modules/nan": { 462 | "version": "2.17.0", 463 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", 464 | "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", 465 | "optional": true 466 | }, 467 | "node_modules/node-fetch": { 468 | "version": "2.6.9", 469 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", 470 | "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", 471 | "dependencies": { 472 | "whatwg-url": "^5.0.0" 473 | }, 474 | "engines": { 475 | "node": "4.x || >=6.0.0" 476 | }, 477 | "peerDependencies": { 478 | "encoding": "^0.1.0" 479 | }, 480 | "peerDependenciesMeta": { 481 | "encoding": { 482 | "optional": true 483 | } 484 | } 485 | }, 486 | "node_modules/once": { 487 | "version": "1.4.0", 488 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 489 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 490 | "dependencies": { 491 | "wrappy": "1" 492 | } 493 | }, 494 | "node_modules/openai": { 495 | "version": "3.2.1", 496 | "resolved": "https://registry.npmjs.org/openai/-/openai-3.2.1.tgz", 497 | "integrity": "sha512-762C9BNlJPbjjlWZi4WYK9iM2tAVAv0uUp1UmI34vb0CN5T2mjB/qM6RYBmNKMh/dN9fC+bxqPwWJZUTWW052A==", 498 | "dependencies": { 499 | "axios": "^0.26.0", 500 | "form-data": "^4.0.0" 501 | } 502 | }, 503 | "node_modules/pump": { 504 | "version": "3.0.0", 505 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 506 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 507 | "dependencies": { 508 | "end-of-stream": "^1.1.0", 509 | "once": "^1.3.1" 510 | } 511 | }, 512 | "node_modules/readable-stream": { 513 | "version": "3.6.2", 514 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 515 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 516 | "dependencies": { 517 | "inherits": "^2.0.3", 518 | "string_decoder": "^1.1.1", 519 | "util-deprecate": "^1.0.1" 520 | }, 521 | "engines": { 522 | "node": ">= 6" 523 | } 524 | }, 525 | "node_modules/safe-buffer": { 526 | "version": "5.2.1", 527 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 528 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 529 | "funding": [ 530 | { 531 | "type": "github", 532 | "url": "https://github.com/sponsors/feross" 533 | }, 534 | { 535 | "type": "patreon", 536 | "url": "https://www.patreon.com/feross" 537 | }, 538 | { 539 | "type": "consulting", 540 | "url": "https://feross.org/support" 541 | } 542 | ] 543 | }, 544 | "node_modules/safer-buffer": { 545 | "version": "2.1.2", 546 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 547 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 548 | }, 549 | "node_modules/split-ca": { 550 | "version": "1.0.1", 551 | "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", 552 | "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==" 553 | }, 554 | "node_modules/ssh2": { 555 | "version": "1.11.0", 556 | "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.11.0.tgz", 557 | "integrity": "sha512-nfg0wZWGSsfUe/IBJkXVll3PEZ//YH2guww+mP88gTpuSU4FtZN7zu9JoeTGOyCNx2dTDtT9fOpWwlzyj4uOOw==", 558 | "hasInstallScript": true, 559 | "dependencies": { 560 | "asn1": "^0.2.4", 561 | "bcrypt-pbkdf": "^1.0.2" 562 | }, 563 | "engines": { 564 | "node": ">=10.16.0" 565 | }, 566 | "optionalDependencies": { 567 | "cpu-features": "~0.0.4", 568 | "nan": "^2.16.0" 569 | } 570 | }, 571 | "node_modules/string_decoder": { 572 | "version": "1.3.0", 573 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 574 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 575 | "dependencies": { 576 | "safe-buffer": "~5.2.0" 577 | } 578 | }, 579 | "node_modules/tar": { 580 | "version": "6.1.13", 581 | "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", 582 | "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", 583 | "dependencies": { 584 | "chownr": "^2.0.0", 585 | "fs-minipass": "^2.0.0", 586 | "minipass": "^4.0.0", 587 | "minizlib": "^2.1.1", 588 | "mkdirp": "^1.0.3", 589 | "yallist": "^4.0.0" 590 | }, 591 | "engines": { 592 | "node": ">=10" 593 | } 594 | }, 595 | "node_modules/tar-fs": { 596 | "version": "2.0.1", 597 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", 598 | "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", 599 | "dependencies": { 600 | "chownr": "^1.1.1", 601 | "mkdirp-classic": "^0.5.2", 602 | "pump": "^3.0.0", 603 | "tar-stream": "^2.0.0" 604 | } 605 | }, 606 | "node_modules/tar-fs/node_modules/chownr": { 607 | "version": "1.1.4", 608 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 609 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" 610 | }, 611 | "node_modules/tar-stream": { 612 | "version": "2.2.0", 613 | "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", 614 | "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", 615 | "dependencies": { 616 | "bl": "^4.0.3", 617 | "end-of-stream": "^1.4.1", 618 | "fs-constants": "^1.0.0", 619 | "inherits": "^2.0.3", 620 | "readable-stream": "^3.1.1" 621 | }, 622 | "engines": { 623 | "node": ">=6" 624 | } 625 | }, 626 | "node_modules/tr46": { 627 | "version": "0.0.3", 628 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 629 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 630 | }, 631 | "node_modules/tweetnacl": { 632 | "version": "0.14.5", 633 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 634 | "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" 635 | }, 636 | "node_modules/util-deprecate": { 637 | "version": "1.0.2", 638 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 639 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 640 | }, 641 | "node_modules/webidl-conversions": { 642 | "version": "3.0.1", 643 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 644 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 645 | }, 646 | "node_modules/whatwg-url": { 647 | "version": "5.0.0", 648 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 649 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 650 | "dependencies": { 651 | "tr46": "~0.0.3", 652 | "webidl-conversions": "^3.0.0" 653 | } 654 | }, 655 | "node_modules/wrappy": { 656 | "version": "1.0.2", 657 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 658 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 659 | }, 660 | "node_modules/yallist": { 661 | "version": "4.0.0", 662 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 663 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 664 | } 665 | }, 666 | "dependencies": { 667 | "@balena/dockerignore": { 668 | "version": "1.0.2", 669 | "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", 670 | "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==" 671 | }, 672 | "@types/docker-modem": { 673 | "version": "3.0.2", 674 | "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.2.tgz", 675 | "integrity": "sha512-qC7prjoEYR2QEe6SmCVfB1x3rfcQtUr1n4x89+3e0wSTMQ/KYCyf+/RAA9n2tllkkNc6//JMUZePdFRiGIWfaQ==", 676 | "dev": true, 677 | "requires": { 678 | "@types/node": "*", 679 | "@types/ssh2": "*" 680 | } 681 | }, 682 | "@types/dockerode": { 683 | "version": "3.3.16", 684 | "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.16.tgz", 685 | "integrity": "sha512-ZAX2VrkTjwjk1808T8m6vMr+CFXSLiDD+tkEkLThI+v83AfzlYQZEWfZKwFyk1PWopSXkdDunmIhrF7sxt+zWg==", 686 | "dev": true, 687 | "requires": { 688 | "@types/docker-modem": "*", 689 | "@types/node": "*" 690 | } 691 | }, 692 | "@types/node": { 693 | "version": "18.15.11", 694 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", 695 | "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", 696 | "dev": true 697 | }, 698 | "@types/node-fetch": { 699 | "version": "2.6.3", 700 | "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.3.tgz", 701 | "integrity": "sha512-ETTL1mOEdq/sxUtgtOhKjyB2Irra4cjxksvcMUR5Zr4n+PxVhsCD9WS46oPbHL3et9Zde7CNRr+WUNlcHvsX+w==", 702 | "dev": true, 703 | "requires": { 704 | "@types/node": "*", 705 | "form-data": "^3.0.0" 706 | }, 707 | "dependencies": { 708 | "form-data": { 709 | "version": "3.0.1", 710 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", 711 | "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", 712 | "dev": true, 713 | "requires": { 714 | "asynckit": "^0.4.0", 715 | "combined-stream": "^1.0.8", 716 | "mime-types": "^2.1.12" 717 | } 718 | } 719 | } 720 | }, 721 | "@types/ssh2": { 722 | "version": "1.11.8", 723 | "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.11.8.tgz", 724 | "integrity": "sha512-BsD9yrKmD8avjbR+N5tvv0jxYHzizcrC156YkPbNjqbu81tCm4ZdS7D6KtXbZfz+CFHgFrTC7j046Lr39W5eig==", 725 | "dev": true, 726 | "requires": { 727 | "@types/node": "^18.11.18" 728 | } 729 | }, 730 | "@types/tar": { 731 | "version": "6.1.4", 732 | "resolved": "https://registry.npmjs.org/@types/tar/-/tar-6.1.4.tgz", 733 | "integrity": "sha512-Cp4oxpfIzWt7mr2pbhHT2OTXGMAL0szYCzuf8lRWyIMCgsx6/Hfc3ubztuhvzXHXgraTQxyOCmmg7TDGIMIJJQ==", 734 | "dev": true, 735 | "requires": { 736 | "@types/node": "*", 737 | "minipass": "^4.0.0" 738 | } 739 | }, 740 | "asn1": { 741 | "version": "0.2.6", 742 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", 743 | "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", 744 | "requires": { 745 | "safer-buffer": "~2.1.0" 746 | } 747 | }, 748 | "asynckit": { 749 | "version": "0.4.0", 750 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 751 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 752 | }, 753 | "axios": { 754 | "version": "0.26.1", 755 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", 756 | "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", 757 | "requires": { 758 | "follow-redirects": "^1.14.8" 759 | } 760 | }, 761 | "base64-js": { 762 | "version": "1.5.1", 763 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 764 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 765 | }, 766 | "bcrypt-pbkdf": { 767 | "version": "1.0.2", 768 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 769 | "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", 770 | "requires": { 771 | "tweetnacl": "^0.14.3" 772 | } 773 | }, 774 | "bl": { 775 | "version": "4.1.0", 776 | "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", 777 | "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", 778 | "requires": { 779 | "buffer": "^5.5.0", 780 | "inherits": "^2.0.4", 781 | "readable-stream": "^3.4.0" 782 | } 783 | }, 784 | "buffer": { 785 | "version": "5.7.1", 786 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 787 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 788 | "requires": { 789 | "base64-js": "^1.3.1", 790 | "ieee754": "^1.1.13" 791 | } 792 | }, 793 | "buildcheck": { 794 | "version": "0.0.3", 795 | "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.3.tgz", 796 | "integrity": "sha512-pziaA+p/wdVImfcbsZLNF32EiWyujlQLwolMqUQE8xpKNOH7KmZQaY8sXN7DGOEzPAElo9QTaeNRfGnf3iOJbA==", 797 | "optional": true 798 | }, 799 | "chownr": { 800 | "version": "2.0.0", 801 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", 802 | "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" 803 | }, 804 | "combined-stream": { 805 | "version": "1.0.8", 806 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 807 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 808 | "requires": { 809 | "delayed-stream": "~1.0.0" 810 | } 811 | }, 812 | "cpu-features": { 813 | "version": "0.0.4", 814 | "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.4.tgz", 815 | "integrity": "sha512-fKiZ/zp1mUwQbnzb9IghXtHtDoTMtNeb8oYGx6kX2SYfhnG0HNdBEBIzB9b5KlXu5DQPhfy3mInbBxFcgwAr3A==", 816 | "optional": true, 817 | "requires": { 818 | "buildcheck": "0.0.3", 819 | "nan": "^2.15.0" 820 | } 821 | }, 822 | "debug": { 823 | "version": "4.3.4", 824 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 825 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 826 | "requires": { 827 | "ms": "2.1.2" 828 | } 829 | }, 830 | "delayed-stream": { 831 | "version": "1.0.0", 832 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 833 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" 834 | }, 835 | "docker-modem": { 836 | "version": "3.0.8", 837 | "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.8.tgz", 838 | "integrity": "sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==", 839 | "requires": { 840 | "debug": "^4.1.1", 841 | "readable-stream": "^3.5.0", 842 | "split-ca": "^1.0.1", 843 | "ssh2": "^1.11.0" 844 | } 845 | }, 846 | "dockerode": { 847 | "version": "3.3.5", 848 | "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.3.5.tgz", 849 | "integrity": "sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==", 850 | "requires": { 851 | "@balena/dockerignore": "^1.0.2", 852 | "docker-modem": "^3.0.0", 853 | "tar-fs": "~2.0.1" 854 | } 855 | }, 856 | "dotenv": { 857 | "version": "16.0.3", 858 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", 859 | "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" 860 | }, 861 | "end-of-stream": { 862 | "version": "1.4.4", 863 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 864 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 865 | "requires": { 866 | "once": "^1.4.0" 867 | } 868 | }, 869 | "follow-redirects": { 870 | "version": "1.15.2", 871 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", 872 | "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" 873 | }, 874 | "form-data": { 875 | "version": "4.0.0", 876 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 877 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 878 | "requires": { 879 | "asynckit": "^0.4.0", 880 | "combined-stream": "^1.0.8", 881 | "mime-types": "^2.1.12" 882 | } 883 | }, 884 | "fs": { 885 | "version": "0.0.1-security", 886 | "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", 887 | "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" 888 | }, 889 | "fs-constants": { 890 | "version": "1.0.0", 891 | "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", 892 | "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" 893 | }, 894 | "fs-minipass": { 895 | "version": "2.1.0", 896 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", 897 | "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", 898 | "requires": { 899 | "minipass": "^3.0.0" 900 | }, 901 | "dependencies": { 902 | "minipass": { 903 | "version": "3.3.6", 904 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", 905 | "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", 906 | "requires": { 907 | "yallist": "^4.0.0" 908 | } 909 | } 910 | } 911 | }, 912 | "ieee754": { 913 | "version": "1.2.1", 914 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 915 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 916 | }, 917 | "inherits": { 918 | "version": "2.0.4", 919 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 920 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 921 | }, 922 | "json5": { 923 | "version": "2.2.3", 924 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 925 | "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" 926 | }, 927 | "mime-db": { 928 | "version": "1.52.0", 929 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 930 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 931 | }, 932 | "mime-types": { 933 | "version": "2.1.35", 934 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 935 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 936 | "requires": { 937 | "mime-db": "1.52.0" 938 | } 939 | }, 940 | "minipass": { 941 | "version": "4.2.5", 942 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.5.tgz", 943 | "integrity": "sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==" 944 | }, 945 | "minizlib": { 946 | "version": "2.1.2", 947 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", 948 | "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", 949 | "requires": { 950 | "minipass": "^3.0.0", 951 | "yallist": "^4.0.0" 952 | }, 953 | "dependencies": { 954 | "minipass": { 955 | "version": "3.3.6", 956 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", 957 | "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", 958 | "requires": { 959 | "yallist": "^4.0.0" 960 | } 961 | } 962 | } 963 | }, 964 | "mkdirp": { 965 | "version": "1.0.4", 966 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 967 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" 968 | }, 969 | "mkdirp-classic": { 970 | "version": "0.5.3", 971 | "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", 972 | "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" 973 | }, 974 | "ms": { 975 | "version": "2.1.2", 976 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 977 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 978 | }, 979 | "nan": { 980 | "version": "2.17.0", 981 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", 982 | "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", 983 | "optional": true 984 | }, 985 | "node-fetch": { 986 | "version": "2.6.9", 987 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", 988 | "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", 989 | "requires": { 990 | "whatwg-url": "^5.0.0" 991 | } 992 | }, 993 | "once": { 994 | "version": "1.4.0", 995 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 996 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 997 | "requires": { 998 | "wrappy": "1" 999 | } 1000 | }, 1001 | "openai": { 1002 | "version": "3.2.1", 1003 | "resolved": "https://registry.npmjs.org/openai/-/openai-3.2.1.tgz", 1004 | "integrity": "sha512-762C9BNlJPbjjlWZi4WYK9iM2tAVAv0uUp1UmI34vb0CN5T2mjB/qM6RYBmNKMh/dN9fC+bxqPwWJZUTWW052A==", 1005 | "requires": { 1006 | "axios": "^0.26.0", 1007 | "form-data": "^4.0.0" 1008 | } 1009 | }, 1010 | "pump": { 1011 | "version": "3.0.0", 1012 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 1013 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 1014 | "requires": { 1015 | "end-of-stream": "^1.1.0", 1016 | "once": "^1.3.1" 1017 | } 1018 | }, 1019 | "readable-stream": { 1020 | "version": "3.6.2", 1021 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 1022 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 1023 | "requires": { 1024 | "inherits": "^2.0.3", 1025 | "string_decoder": "^1.1.1", 1026 | "util-deprecate": "^1.0.1" 1027 | } 1028 | }, 1029 | "safe-buffer": { 1030 | "version": "5.2.1", 1031 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1032 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1033 | }, 1034 | "safer-buffer": { 1035 | "version": "2.1.2", 1036 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1037 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1038 | }, 1039 | "split-ca": { 1040 | "version": "1.0.1", 1041 | "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", 1042 | "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==" 1043 | }, 1044 | "ssh2": { 1045 | "version": "1.11.0", 1046 | "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.11.0.tgz", 1047 | "integrity": "sha512-nfg0wZWGSsfUe/IBJkXVll3PEZ//YH2guww+mP88gTpuSU4FtZN7zu9JoeTGOyCNx2dTDtT9fOpWwlzyj4uOOw==", 1048 | "requires": { 1049 | "asn1": "^0.2.4", 1050 | "bcrypt-pbkdf": "^1.0.2", 1051 | "cpu-features": "~0.0.4", 1052 | "nan": "^2.16.0" 1053 | } 1054 | }, 1055 | "string_decoder": { 1056 | "version": "1.3.0", 1057 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 1058 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1059 | "requires": { 1060 | "safe-buffer": "~5.2.0" 1061 | } 1062 | }, 1063 | "tar": { 1064 | "version": "6.1.13", 1065 | "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", 1066 | "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", 1067 | "requires": { 1068 | "chownr": "^2.0.0", 1069 | "fs-minipass": "^2.0.0", 1070 | "minipass": "^4.0.0", 1071 | "minizlib": "^2.1.1", 1072 | "mkdirp": "^1.0.3", 1073 | "yallist": "^4.0.0" 1074 | } 1075 | }, 1076 | "tar-fs": { 1077 | "version": "2.0.1", 1078 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", 1079 | "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", 1080 | "requires": { 1081 | "chownr": "^1.1.1", 1082 | "mkdirp-classic": "^0.5.2", 1083 | "pump": "^3.0.0", 1084 | "tar-stream": "^2.0.0" 1085 | }, 1086 | "dependencies": { 1087 | "chownr": { 1088 | "version": "1.1.4", 1089 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 1090 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" 1091 | } 1092 | } 1093 | }, 1094 | "tar-stream": { 1095 | "version": "2.2.0", 1096 | "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", 1097 | "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", 1098 | "requires": { 1099 | "bl": "^4.0.3", 1100 | "end-of-stream": "^1.4.1", 1101 | "fs-constants": "^1.0.0", 1102 | "inherits": "^2.0.3", 1103 | "readable-stream": "^3.1.1" 1104 | } 1105 | }, 1106 | "tr46": { 1107 | "version": "0.0.3", 1108 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 1109 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 1110 | }, 1111 | "tweetnacl": { 1112 | "version": "0.14.5", 1113 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 1114 | "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" 1115 | }, 1116 | "util-deprecate": { 1117 | "version": "1.0.2", 1118 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1119 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 1120 | }, 1121 | "webidl-conversions": { 1122 | "version": "3.0.1", 1123 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 1124 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 1125 | }, 1126 | "whatwg-url": { 1127 | "version": "5.0.0", 1128 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 1129 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 1130 | "requires": { 1131 | "tr46": "~0.0.3", 1132 | "webidl-conversions": "^3.0.0" 1133 | } 1134 | }, 1135 | "wrappy": { 1136 | "version": "1.0.2", 1137 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1138 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 1139 | }, 1140 | "yallist": { 1141 | "version": "4.0.0", 1142 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1143 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1144 | } 1145 | } 1146 | } 1147 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitwit", 3 | "version": "0.1.7", 4 | "description": "", 5 | "main": "index.ts", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "tsc --module commonjs && node dist/src/index.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "dockerode": "^3.3.5", 14 | "dotenv": "^16.0.3", 15 | "fs": "^0.0.1-security", 16 | "json5": "^2.2.3", 17 | "node-fetch": "^2.6.9", 18 | "openai": "^3.2.1", 19 | "tar": "^6.1.13" 20 | }, 21 | "devDependencies": { 22 | "@types/dockerode": "^3.3.16", 23 | "@types/node": "^18.15.11", 24 | "@types/node-fetch": "^2.6.3", 25 | "@types/tar": "^6.1.4" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /prompt.ts: -------------------------------------------------------------------------------- 1 | const newProjectPrompt = ` 2 | Help me to create a repository containing the code for: 3 | - {DESCRIPTION} 4 | 5 | The project name is: {REPOSITORY_NAME} 6 | 7 | Give me instructions to generate complete, working code for this project, but use only shell commands. No need to give further explanation. All of your output must be given as a single /bin/sh script. 8 | 9 | For example, when want to edit a file, use the command \`echo "file contents" > filename.ext\`. Create intermediate directories before writing files. 10 | 11 | At the beginning of the script, create a git repo in the current directory before running any other commands. Then, create a .gitignore file. 12 | 13 | Assume all commands will be run on a clean \`{BASE_IMAGE}\` system with no packages installed. 14 | 15 | Follow each command in the instructions with a git commit command including a detailed commit message. 16 | 17 | The repository should also contain a helpful README.md file. The README should include a high-level descrition of the code, a list of software needed to run the code, and basic instructions on how to run the application. The README should not include any text or information about a software license. This project has no license. 18 | 19 | Do not include any commands to run, start or deploy or test the app. 20 | 21 | Do not use an exit command at the end of the script. 22 | `; 23 | 24 | const changeProjectPrompt = ` 25 | {FILE_CONTENTS} 26 | 27 | The above are contents of existing files in my project. Help me to modify the repository with the following changes only: 28 | 29 | {DESCRIPTION} 30 | 31 | {CHANGE_PREVIEW} 32 | - install any dependencies that were added in the above steps 33 | 34 | Give me instructions to modify the repository, but use only shell commands. Use as few commands as possible. Use the shortest commands possible. Provide a single /bin/sh script with no extra explanation. Complete all code and don't leave placeholder code. 35 | 36 | Do not include any commands to run, start or deploy or test the app. 37 | 38 | Follow each command in the instructions with a git commit command including a detailed commit message. 39 | `; 40 | 41 | const planChangesPrompt = ` 42 | {FILE_LIST} 43 | The above is my project structure. Help me to modify the repository with the following changes only: 44 | - {DESCRIPTION} 45 | 46 | By giving an array of the files I need to edit and new files I need to add to accomplish this, using the format below: 47 | [ 48 | ["filename", "add|edit","description of changes to be made"], 49 | ... 50 | ] 51 | The list should have as few items as possible, ideally, one, two or three items, and an absolute maximum of five items. 52 | 53 | Only give the array. No code fences, no other text. 54 | `; 55 | 56 | export { newProjectPrompt, changeProjectPrompt, planChangesPrompt }; 57 | -------------------------------------------------------------------------------- /scripts.ts: -------------------------------------------------------------------------------- 1 | // Setup the git config. 2 | export const SETUP_GIT_CONFIG = ` 3 | git config --global user.email {GIT_AUTHOR_EMAIL} 4 | git config --global user.name {GIT_AUTHOR_NAME} 5 | git config --global init.defaultBranch main 6 | `; 7 | 8 | // Make a new directory. 9 | export const MAKE_PROJECT_DIR = ` 10 | cd ~ 11 | mkdir {REPO_NAME} 12 | cd {REPO_NAME} 13 | `; 14 | 15 | // Run the build script and change to the script's final directory. 16 | export const RUN_BUILD_SCRIPT = ` 17 | source /app/build.sh > /app/build.log 2>&1 18 | `; 19 | 20 | // Change to the top-level directory of the git repository. 21 | export const CD_GIT_ROOT = ` 22 | cd $(git rev-parse --show-toplevel) 23 | ` 24 | 25 | export const GET_BUILD_LOG = ` 26 | cat /app/build.log 27 | ` 28 | 29 | // Configure the git credentials. 30 | export const SETUP_GIT_CREDENTIALS = ` 31 | echo "https://{GITHUB_USERNAME}:{GITHUB_TOKEN}@github.com" >> ~/.git-credentials 32 | git config --global credential.helper store 33 | ` 34 | 35 | // Push the main branch to the remote repository. 36 | export const PUSH_TO_REPO = ` 37 | git branch -M main 38 | git remote add origin {PUSH_URL} 39 | git push -u origin main 40 | ` 41 | 42 | // Clone an existing repository. 43 | export const CLONE_PROJECT_REPO = ` 44 | cd ~ 45 | git clone https://{GITHUB_USERNAME}:{GITHUB_TOKEN}@github.com/{FULL_REPO_NAME}.git 46 | cd {REPO_NAME} 47 | ` 48 | 49 | // Get the contents of the repository. 50 | export const GET_FILE_LIST = ` 51 | cd ~/{REPO_NAME} 52 | find . -path "./.git" -prune -o -type f -print 53 | ` 54 | 55 | // Create a new git branch. 56 | export const CREATE_NEW_BRANCH = ` 57 | cd ~/{REPO_NAME} 58 | git checkout {SOURCE_BRANCH_NAME} 59 | git checkout -b {BRANCH_NAME} 60 | ` 61 | 62 | // Push the new branch to the remote repository. 63 | export const PUSH_BRANCH = ` 64 | git push -u origin {BRANCH_NAME} 65 | ` 66 | -------------------------------------------------------------------------------- /src/helpers/cli.ts: -------------------------------------------------------------------------------- 1 | import * as readline from "readline" 2 | import * as fs from "fs" 3 | import { Build } from "../lib/build" 4 | import { writeFile, readFile } from "fs/promises" 5 | 6 | function askQuestion(query: string): Promise { 7 | const rl = readline.createInterface({ 8 | input: process.stdin, 9 | output: process.stdout, 10 | }) 11 | 12 | return new Promise((resolve) => 13 | rl.question(query, (ans) => { 14 | rl.close() 15 | resolve(ans) 16 | }) 17 | ) 18 | } 19 | 20 | export async function cli(): Promise { 21 | if (!fs.existsSync("./build")) { 22 | fs.mkdirSync("./build") 23 | } 24 | 25 | const again = process.argv.includes("--again") // Use user input from the last run. 26 | const offline = process.argv.includes("--offline") // Use build script from the last run. 27 | const debug = process.argv.includes("--debug") // Leave the container running to debug. 28 | const branch = process.argv.includes("--branch") 29 | 30 | let userInput, suggestedName, sourceGitURL 31 | 32 | // Detect metadata from a previous run. 33 | if (offline || again) { 34 | ({ userInput, suggestedName, sourceGitURL } = JSON.parse( 35 | (await readFile("./build/info.json")).toString() 36 | )) 37 | } 38 | 39 | if (!userInput || !suggestedName) { 40 | if (branch) { 41 | sourceGitURL = await askQuestion("Source repository URL: ") 42 | } else { 43 | console.log("Let's cook up a new project!") 44 | } 45 | userInput = await askQuestion("What would you like to make? ") 46 | suggestedName = await askQuestion(branch ? "New branch name:" : "Repository name: ") 47 | await writeFile("./build/info.json", JSON.stringify({ userInput, suggestedName, sourceGitURL })) 48 | } 49 | 50 | let project = await Build.create({ 51 | buildType: branch ? "BRANCH" : "REPOSITORY", 52 | suggestedName, 53 | userInput, 54 | creator: process.env.GITHUB_USERNAME!, 55 | sourceGitURL 56 | }); 57 | 58 | if (offline) { 59 | const completionFile = await readFile("./build/completion.json"); 60 | const infoFile = await readFile("./build/info.json"); 61 | const text = completionFile.toString() 62 | const { id, model } = JSON.parse(infoFile.toString()) 63 | project.completion = { text, id, model } 64 | } 65 | 66 | await project.buildAndPush({ debug }) 67 | 68 | if (!offline) { 69 | let { text } = project.completion 70 | await writeFile("./build/completion.json", text!) 71 | } 72 | } -------------------------------------------------------------------------------- /src/helpers/container.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as tar from 'tar'; 3 | import * as Docker from 'dockerode'; 4 | 5 | const cleanOutput = (textInput: string) => { 6 | return textInput 7 | .replace(/[^\x00-\x7F]+/g, "") // Remove non-ASCII characters 8 | .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/g, "") // Remove ASCII control characters 9 | } 10 | 11 | async function createContainer(docker: Docker, tag: string, environment?: string[]): Promise { 12 | // create a new container from the image 13 | return await docker.createContainer({ 14 | Image: tag, // specify the image to use 15 | Env: environment ?? [], 16 | Tty: true, 17 | Cmd: ['/bin/sh'], 18 | OpenStdin: true, 19 | }); 20 | } 21 | 22 | async function startContainer(container: Docker.Container) { 23 | process.on('SIGINT', async function () { 24 | console.log("Caught interrupt signal"); 25 | await container.stop({ force: true }); 26 | }); 27 | await container.start(); 28 | const stream = await container.logs({ 29 | follow: true, 30 | stdout: true, 31 | stderr: true 32 | }); 33 | stream.on('data', chunk => console.log(chunk.toString())); 34 | return stream; 35 | } 36 | 37 | async function waitForStreamEnd(stream: NodeJS.ReadableStream): Promise { 38 | return new Promise((resolve, reject) => { 39 | try { 40 | stream.on('end', async () => { 41 | resolve(); 42 | }); 43 | } catch (err) { 44 | reject(err); 45 | } 46 | }); 47 | } 48 | 49 | async function runCommandInContainer(container: Docker.Container, command: string[], silent: boolean = false): Promise { 50 | const exec = await container.exec({ 51 | Cmd: command, 52 | AttachStdout: true, 53 | AttachStderr: true, 54 | }); 55 | const stream = await exec.start({ hijack: true, stdin: true }); 56 | let output = ""; 57 | stream.on('data', (data) => { 58 | output += data; 59 | }); 60 | await waitForStreamEnd(stream); 61 | if (!silent) console.log(output); 62 | return cleanOutput(output); 63 | } 64 | 65 | async function runScriptInContainer(container: Docker.Container, script: string, parameters: { [key: string]: string }, silent: boolean = false) { 66 | // Substitutes values in the template string. 67 | const replaceParameters = (templateString: string, parameters: { [key: string]: string }): string => { 68 | return Object.keys(parameters).reduce( 69 | (acc, key) => acc.replace(new RegExp(`{${key}}`, "g"), parameters[key] ?? ""), 70 | templateString 71 | ); 72 | }; 73 | 74 | // Run the given script as a bash script. 75 | const result = await runCommandInContainer(container, ["bash", "-c", replaceParameters(script, parameters)], silent) 76 | return result; 77 | } 78 | 79 | async function readFileFromContainer(container: Docker.Container, path: string) { 80 | return await runCommandInContainer(container, ["cat", path], true) 81 | } 82 | 83 | async function copyFileToContainer(container: Docker.Container, localFilePath: string, containerFilePath: string) { 84 | const baseDir = path.dirname(localFilePath); 85 | const archive = tar.create({ gzip: false, portable: true, cwd: baseDir }, [path.basename(localFilePath)]); 86 | await container.putArchive(archive, { path: containerFilePath }); 87 | } 88 | 89 | export { createContainer, startContainer, runCommandInContainer, runScriptInContainer, copyFileToContainer, readFileFromContainer } 90 | -------------------------------------------------------------------------------- /src/helpers/corrections.ts: -------------------------------------------------------------------------------- 1 | // Post-processing: 2 | 3 | function applyCorrections(buildScript: string) { 4 | 5 | // Detect commands like: echo -e "..." > ... 6 | const echoRedirection = /^((?echo -e "(?:\\.|[^\\"])*") > (?.*\/).*)$/mg; 7 | // Detect commands like: echo -e '...' > ... 8 | const echoRedirectionSingleQuote = /^((?echo -e '(?:\\.|[^\\'])*') > (?.*\/).*)$/mg; 9 | 10 | // These small corrections are necessary as they take into account the random characters created by terminal. 11 | return buildScript.replace(/^npx /mg, 'npx --yes ') 12 | .replace(/(^\`\`\`[a-z]*\n|\n\`\`\`$)/g, '') 13 | .replace(/^echo( -e)? /mg, 'echo -e ') 14 | .replace(/^npm install /mg, 'npm install --package-lock-only ') 15 | .replace(echoRedirection, 'mkdir -p $ && $1') 16 | .replace(echoRedirectionSingleQuote, 'mkdir -p $ && $1') 17 | .replace(/^git (remote|push|checkout|merge|branch) .*$/mg, '') 18 | .replace(/^(az|aws|systemctl) .*$/mg, '') 19 | .replace(/^git commit (.*)$/mg, 'git commit -a $1') 20 | .replace(/^sed -i '' /mg, 'sed -i '); 21 | } 22 | 23 | export { applyCorrections }; 24 | -------------------------------------------------------------------------------- /src/helpers/github.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch" 2 | 3 | function errorMessage(result: any) { 4 | if (result.message) { 5 | let message = `${result.message}`; 6 | if (result.errors) { 7 | for (const error of result.errors) { 8 | message += ` ${error.resource} ${error.message}.`; 9 | } 10 | } 11 | return message; 12 | } 13 | return undefined; 14 | } 15 | 16 | function incrementName(name: string) { 17 | const regex = /\-(\d+)$/; 18 | const match = name.match(regex); 19 | if (match) { 20 | const number = parseInt(match[1]!); 21 | return name.replace(regex, `-${number + 1}`); 22 | } else { 23 | return `${name}-1`; 24 | } 25 | } 26 | 27 | interface GitHubRepoOptions { 28 | token: string; 29 | name: string; 30 | description: string; 31 | org?: string; 32 | template?: { repository: string, owner: string } 33 | attempts?: number; 34 | } 35 | 36 | async function createGitHubRepo({ token, name, description, org, template, attempts = 10 }: GitHubRepoOptions) { 37 | let failedAttempts = 0; 38 | let currentName = name; 39 | let result: any = {}; 40 | 41 | // Try new names until we find one that doesn't exist, or we run out of attempts. 42 | while (failedAttempts < attempts) { 43 | 44 | // Request to the GitHub API. 45 | const requestOptions = { 46 | method: 'POST', 47 | headers: { 48 | 'Authorization': `token ${token}`, 49 | 'Content-Type': 'application/json' 50 | }, 51 | body: JSON.stringify({ 52 | name: currentName, 53 | description: description.replace(/\n/g, "").trim().slice(0, 350), 54 | private: true, 55 | // If generating from a template and owner is an organization, specify the owner. 56 | ...(template && org && { owner: org }) 57 | }) 58 | }; 59 | 60 | // Create the repo at username/repo or org/repo. 61 | const response = template 62 | // Generate from a template repository: 63 | ? await fetch(`https://api.github.com/repos/${template.owner}/${template.repository}/generate`, requestOptions) 64 | // Create a new repository: 65 | : org 66 | ? await fetch(`https://api.github.com/orgs/${org}/repos`, requestOptions) 67 | : await fetch('https://api.github.com/user/repos', requestOptions); 68 | result = await response.json() ?? {}; 69 | 70 | // If the repo already exists, add a number to the end of the name. 71 | const alreadyExists = (errors: Record[]): boolean => errors 72 | && errors[0].field === "name" // When creating a new repository. 73 | || errors[0].includes("already exists") // When generating from a template. 74 | 75 | if (result.errors && alreadyExists(result.errors)) { 76 | console.log(`Repository name already exists. Trying ${currentName}.`) 77 | failedAttempts++ 78 | currentName = incrementName(currentName); 79 | } else { 80 | break; 81 | } 82 | } 83 | 84 | // Throw an error if repository creation failed. 85 | const message = errorMessage(result); 86 | if (message) { 87 | console.log(result) 88 | throw new Error("Failed to create repository: " + message) 89 | } 90 | 91 | return result; 92 | } 93 | 94 | async function addGitHubCollaborator(token: string, repoName: string, collaborator: string) { 95 | // Add collaborator to the repo. 96 | // Note: Repo name is in the format of "org/repo". 97 | const requestOptions = { 98 | method: 'PUT', 99 | headers: { 100 | 'Authorization': `token ${token}`, 101 | 'Content-Type': 'application/json' 102 | }, 103 | body: JSON.stringify({ 104 | permission: 'push' 105 | }) 106 | }; 107 | 108 | const response = await fetch(`https://api.github.com/repos/${repoName}/collaborators/${collaborator}`, requestOptions); 109 | if (response.status >= 200 && response.status < 300) { 110 | return true; 111 | } else { 112 | const result = await response.json(); 113 | // Print errors if there are any. 114 | const message = errorMessage(result); 115 | if (message) { 116 | console.log(result); 117 | throw new Error("Failed to add collaborator: " + message) 118 | } 119 | return result; 120 | } 121 | } 122 | 123 | async function getGitHubBranches(token: string, repository: string): Promise { 124 | const requestOptions = { 125 | method: 'GET', 126 | headers: { 127 | 'Authorization': `token ${token}`, 128 | 'Content-Type': 'application/json' 129 | } 130 | }; 131 | 132 | const response = await fetch(`https://api.github.com/repos/${repository}/branches`, requestOptions); 133 | if (response.status === 204) { 134 | return []; 135 | } else { 136 | const result = await response.json(); 137 | // Print errors if there are any. 138 | const message = errorMessage(result); 139 | if (message) { 140 | console.log(result) 141 | throw new Error("Failed to get branches: " + message) 142 | } 143 | return result; 144 | } 145 | } 146 | 147 | async function correctBranchName(token: string, sourceRepository: string, branchName: string) { 148 | const branches = await getGitHubBranches(process.env.GITHUB_TOKEN!, sourceRepository) 149 | const branchNames = branches.map((branch) => branch.name) 150 | let correctedName = branchName 151 | while (branchNames.includes(correctedName)) { 152 | correctedName = incrementName(correctedName) 153 | } 154 | return correctedName; 155 | } 156 | 157 | export { createGitHubRepo, addGitHubCollaborator, getGitHubBranches, correctBranchName } 158 | -------------------------------------------------------------------------------- /src/helpers/openai.ts: -------------------------------------------------------------------------------- 1 | import { Configuration, OpenAIApi } from 'openai' 2 | 3 | // OpenAI API: 4 | 5 | export type Completion = { text: string; id: string; model: string } | { error: string }; 6 | 7 | async function simpleOpenAIRequest(prompt: string, config: any): Promise { 8 | 9 | const baseOptions = { 10 | headers: { 11 | "Helicone-Auth": `Bearer ${process.env.HELICONE_API_KEY}`, 12 | ...(process.env.OPENAI_CACHE_ENABLED && { 13 | "Helicone-Cache-Enabled": "true", 14 | "Helicone-Cache-Bucket-Max-Size": "1", 15 | }), 16 | }, 17 | }; 18 | 19 | const configuration = new Configuration({ 20 | apiKey: process.env.OPENAI_API_KEY, 21 | basePath: process.env.OPENAI_BASE_URL, 22 | baseOptions: baseOptions, 23 | }) 24 | const openai = new OpenAIApi(configuration) 25 | 26 | try { 27 | const completion = await openai.createChatCompletion({ 28 | ...config, 29 | messages: [ 30 | { 31 | role: 'user', 32 | content: prompt, 33 | }, 34 | ], 35 | }) 36 | // When the API returns an error: 37 | const data: any = completion.data; 38 | if (data.error) { 39 | throw new Error(`OpenAI error: (${data.error.type}) ${data.error.message}`) 40 | } 41 | return { 42 | text: completion.data.choices[0]!.message!.content, 43 | id: completion.data.id, 44 | model: completion.data.model 45 | }; 46 | } catch (error: any) { 47 | // When any other error occurs: 48 | throw new Error(`Failed to make request. Error message: ${error.message}`); 49 | } 50 | } 51 | 52 | export { simpleOpenAIRequest } 53 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { cli } from "./helpers/cli"; 2 | import * as dotenv from "dotenv" 3 | 4 | dotenv.config(); 5 | 6 | 7 | (async () => { 8 | await cli(); 9 | })().catch((err: any) => { 10 | console.error(err) 11 | process.exit(1) 12 | }) -------------------------------------------------------------------------------- /src/lib/build.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs" 2 | import * as path from "path" 3 | import * as os from "os" 4 | import Docker from 'dockerode' 5 | import packageInfo from '../../package.json'; 6 | 7 | import { 8 | createContainer, 9 | startContainer, 10 | runCommandInContainer, 11 | runScriptInContainer, 12 | copyFileToContainer, 13 | readFileFromContainer 14 | } from "../helpers/container" 15 | import { simpleOpenAIRequest, Completion } from "../helpers/openai" 16 | import { applyCorrections } from "../helpers/corrections" 17 | import { newProjectPrompt, changeProjectPrompt, planChangesPrompt } from "../../prompt" 18 | import { createGitHubRepo, addGitHubCollaborator, correctBranchName } from "../helpers/github" 19 | import * as scripts from "../../scripts" 20 | import { BuildPlan } from "./buildPlan" 21 | 22 | const { exec } = require('child_process'); 23 | 24 | // Container constants: 25 | const baseImage = "node:latest" 26 | const containerHome = "/app/" 27 | 28 | // OpenAI constants: 29 | const gptModel = "gpt-3.5-turbo" 30 | const temperature = 0.2 31 | const maxPromptLength = 5500 32 | 33 | // Reading and writing files: 34 | async function writeFile(path: string, contents: string): Promise { 35 | await fs.promises.writeFile(path, contents) 36 | console.log(`Wrote: ${path}`) 37 | } 38 | 39 | function executeCommand(command: string): Promise { 40 | return new Promise((resolve, reject) => { 41 | exec(command, (error: Error | null, stdout: string, stderr: string) => { 42 | if (error) { 43 | reject(error); 44 | return; 45 | } 46 | 47 | if (stderr) { 48 | reject(new Error(stderr)); 49 | return; 50 | } 51 | 52 | resolve(stdout.trim()); 53 | }); 54 | }); 55 | } 56 | 57 | type BuildType = "REPOSITORY" | "BRANCH" | "TEMPLATE" 58 | type BuildConstructorProps = { 59 | userInput: string, // User prompt 60 | buildType: BuildType, // Build type 61 | suggestedName: string, // Name of new repository or branch 62 | creator: string, // Username to create the repository with 63 | sourceGitURL?: string, // Repository to branch from 64 | sourceBranch?: string, // Branch to branch from 65 | organization?: string, // Oganization to create the repository under 66 | collaborator?: string, // User to add as a collaborator 67 | } 68 | 69 | // Project generation 70 | export class Build { 71 | // Input parameters to create a build: 72 | userInput: string 73 | isBranch?: boolean = false 74 | isCopy?: boolean = false 75 | suggestedName: string 76 | sourceGitURL?: string 77 | sourceBranch?: string 78 | creator: string 79 | organization?: string 80 | collaborator?: string 81 | dockerDaemonPath?: string 82 | 83 | // Generated values: 84 | completion?: any 85 | planCompletion?: any 86 | fileList?: string 87 | buildPlan?: BuildPlan 88 | 89 | // Output parameters: 90 | buildScript?: string 91 | buildLog?: string 92 | outputGitURL?: string 93 | outputHTMLURL?: string 94 | 95 | private constructor(props: BuildConstructorProps) { 96 | // To create a new project: 97 | this.suggestedName = props.suggestedName // The suggested name of the branch 98 | this.userInput = props.userInput // The description of the project. 99 | 100 | // The username(s) to create the repository under. 101 | this.creator = props.creator 102 | this.organization = props.organization 103 | this.collaborator = props.collaborator 104 | 105 | // To create a new branch or fork: 106 | this.isBranch = props.buildType === "BRANCH" 107 | this.isCopy = props.buildType === "TEMPLATE" 108 | 109 | if (this.isBranch || this.isCopy) { 110 | if (!props.sourceGitURL) { 111 | throw new Error("Source repository is required to make a branch.") 112 | } 113 | this.sourceGitURL = props.sourceGitURL // The source repository URL. 114 | this.sourceBranch = props.sourceBranch // The source branch name. 115 | } 116 | } 117 | 118 | // Factory class that only returns a working object if Docker is found 119 | static async create(props: BuildConstructorProps): Promise { 120 | const classInstance = new Build(props); 121 | try { 122 | classInstance.dockerDaemonPath = await executeCommand("lsof -U -n -c docker | awk '/docker\\.sock/ {print $8}' | head -n 1"); 123 | } catch (error: any) { 124 | console.log(`Unable to find a running Docker daemon.`) 125 | throw new Error(`Unable to find a running Docker daemon.`) 126 | } 127 | 128 | return classInstance; 129 | } 130 | 131 | // Generate a build script to create a new repository. 132 | private getCompletion = async (): Promise => { 133 | 134 | console.log("Calling on the great machine god...") 135 | 136 | // Generate a new repository from an empty directory. 137 | const prompt = newProjectPrompt 138 | .replace("{REPOSITORY_NAME}", this.suggestedName) 139 | .replace("{DESCRIPTION}", this.userInput) 140 | .replace("{BASE_IMAGE}", baseImage); 141 | 142 | this.completion = await simpleOpenAIRequest(prompt.slice(-maxPromptLength), { 143 | model: gptModel, 144 | user: this.collaborator ?? this.creator, 145 | temperature: temperature 146 | }); 147 | 148 | console.log("Prayers were answered. (1/1)"); 149 | 150 | return this.completion 151 | } 152 | 153 | // Generate a plan to modify an existing repository. 154 | private getPlanCompletion = async (): Promise => { 155 | 156 | console.log("Generating plan...") 157 | 158 | // Prompt to generate the build plan. 159 | const prompt = planChangesPrompt 160 | .replace("{DESCRIPTION}", this.userInput) 161 | .replace("{FILE_LIST}", this.fileList ?? ""); 162 | 163 | this.planCompletion = await simpleOpenAIRequest(prompt.slice(-maxPromptLength), { 164 | model: gptModel, 165 | user: this.collaborator ?? this.creator, 166 | temperature: temperature 167 | }); 168 | console.log("Completion received. (1/2)") 169 | 170 | return this.planCompletion; 171 | } 172 | 173 | // Generate a build script to modify an existing repository. 174 | private getBranchCompletion = async (previewContext: string, fileContentsContext: string): Promise => { 175 | 176 | // Prompt to generate the build script. 177 | const fullPrompt = changeProjectPrompt 178 | .replace("{DESCRIPTION}", this.userInput) 179 | .replace("{FILE_CONTENTS}", fileContentsContext) 180 | .replace("{CHANGE_PREVIEW}", previewContext); 181 | 182 | this.completion = await simpleOpenAIRequest(fullPrompt.slice(-maxPromptLength), { 183 | model: gptModel, 184 | user: this.collaborator ?? this.creator, 185 | temperature: temperature 186 | }); 187 | 188 | console.log("Completion received. (2/2)"); 189 | 190 | return this.completion 191 | } 192 | 193 | buildAndPush = async ({ debug = false, onStatusUpdate = async ({ }) => { }, } = {}) => { 194 | 195 | // This function pushes a status update to the database. 196 | const updateStatus = async ({ finished = false } = {}) => { 197 | await onStatusUpdate({ 198 | outputGitURL: this.outputGitURL, 199 | outputHTMLURL: this.outputHTMLURL, 200 | buildScript: this.buildScript, 201 | buildLog: this.buildLog, 202 | buildPlan: this.planCompletion?.text, 203 | completionId: this.completion?.id, 204 | planCompletionId: this.planCompletion?.id, 205 | gptModel: this.completion?.model, 206 | gitwitVersion: packageInfo.version, 207 | finished: finished 208 | }) 209 | } 210 | 211 | // Build directory 212 | const buildDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "gitwit-")) + "/" 213 | console.log(`Created temporary directory: ${buildDirectory} `) 214 | 215 | // Get the name of the source repository as username/reponame. 216 | const regex = /\/\/github\.com\/([\w-]+)\/([\w-]+)\.git/ 217 | const [, sourceRepositoryUser, sourceRepositoryName] = this.sourceGitURL?.match(regex) ?? [] 218 | 219 | // Intermediate build script. 220 | const buildScriptPath = buildDirectory + "build.sh" 221 | const buildLogPath = buildDirectory + "build.log" 222 | 223 | // If we're creating a new repository, call the OpenAI API already. 224 | if (!this.isBranch && !this.isCopy && !this.completion) { 225 | await this.getCompletion() 226 | } 227 | 228 | let repositoryName: string | undefined; 229 | let branchName = ""; 230 | 231 | if (this.isBranch) { 232 | // Use the provided repository. 233 | repositoryName = sourceRepositoryName; 234 | console.log(`Using repository: ${this.sourceGitURL} `) 235 | 236 | // Find an available branch name. 237 | branchName = await correctBranchName( 238 | process.env.GITHUB_TOKEN!, 239 | `${sourceRepositoryUser}/${sourceRepositoryName}`, 240 | this.suggestedName! 241 | ) 242 | 243 | this.outputGitURL = this.sourceGitURL; 244 | const sourceHTMLRoot = this.sourceGitURL?.replace(".git", ""); // What is this sourceHTML? 245 | this.outputHTMLURL = `${sourceHTMLRoot}/tree/${branchName}`; 246 | console.log(`Creating branch: ${branchName}`) 247 | } else { 248 | 249 | // Create a new GitHub repository. 250 | const template = { owner: sourceRepositoryUser, repository: sourceRepositoryName }; 251 | const templateOptions = this.isCopy ? { template } : undefined 252 | const newRepository: any = await createGitHubRepo({ 253 | token: process.env.GITHUB_TOKEN!, 254 | name: this.suggestedName, 255 | org: this.organization, 256 | description: this.userInput, 257 | ...templateOptions 258 | }); 259 | 260 | // Outputs from the new repository. 261 | this.outputGitURL = newRepository.clone_url 262 | this.outputHTMLURL = newRepository.html_url 263 | repositoryName = newRepository.name 264 | console.log(`Created repository: ${newRepository.html_url}`) 265 | 266 | // Add the user as a collaborator on the GitHub repository. 267 | if (newRepository.full_name && this.collaborator) { 268 | const result = this.collaborator ? await addGitHubCollaborator( 269 | process.env.GITHUB_TOKEN!, 270 | newRepository.full_name, 271 | this.collaborator! 272 | ) : null 273 | console.log(`Added ${this.collaborator} to ${newRepository.full_name}.`) 274 | } 275 | } 276 | 277 | if (this.isCopy) { 278 | await updateStatus({ finished: true }); 279 | } else { 280 | // Define the parameters used by the scripts. 281 | let parameters = { 282 | REPO_NAME: repositoryName!, 283 | FULL_REPO_NAME: `${sourceRepositoryUser}/${sourceRepositoryName}`, 284 | PUSH_URL: this.outputGitURL!, 285 | REPO_DESCRIPTION: this.userInput!, 286 | GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL!, 287 | GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME!, 288 | GITHUB_USERNAME: process.env.GITHUB_USERNAME!, 289 | GITHUB_TOKEN: process.env.GITHUB_TOKEN!, 290 | GITWIT_VERSION: packageInfo.version, 291 | BRANCH_NAME: branchName ?? "", 292 | SOURCE_BRANCH_NAME: this.sourceBranch ?? "", 293 | GITHUB_ACCOUNT: this.creator, 294 | } 295 | 296 | // Connect to Docker... 297 | console.log( 298 | "Connecting to Docker on " 299 | + (process.env.DOCKER_API_HOST ?? "localhost") 300 | + (process.env.DOCKER_API_PORT ? `:${process.env.DOCKER_API_PORT}` : "") 301 | ); 302 | 303 | const docker = new Docker({ 304 | socketPath: this.dockerDaemonPath!, 305 | host: process.env.DOCKER_API_HOST, 306 | port: process.env.DOCKER_API_PORT, 307 | // Flightcontrol doesn't support environment variables with newlines. 308 | ca: process.env.DOCKER_API_CA?.replace(/\\n/g, "\n"), 309 | cert: process.env.DOCKER_API_CERT?.replace(/\\n/g, "\n"), 310 | key: process.env.DOCKER_API_KEY?.replace(/\\n/g, "\n"), 311 | // We use HTTPS when there is an SSL key. 312 | protocol: process.env.DOCKER_API_KEY ? 'https' : undefined, 313 | }) 314 | 315 | // Create a new docker container. 316 | const container = await createContainer(docker, baseImage) 317 | console.log(`Container ${container.id} created.`) 318 | 319 | // Start the container. 320 | await startContainer(container) 321 | console.log(`Container ${container.id} started.`) 322 | 323 | // Copy the metadata file to the container. 324 | await runCommandInContainer(container, ["mkdir", containerHome]) 325 | 326 | // These scripts are appended together to maintain the current directory. 327 | if (this.isBranch) { 328 | await runScriptInContainer(container, 329 | scripts.SETUP_GIT_CONFIG + // Setup the git commit author 330 | scripts.CLONE_PROJECT_REPO, 331 | parameters); 332 | 333 | // Get a list of files in the repository. 334 | this.fileList = await runScriptInContainer(container, 335 | scripts.GET_FILE_LIST, 336 | parameters, true); 337 | 338 | // Use ChatGPT to generate a plan. 339 | await this.getPlanCompletion() 340 | this.buildPlan = new BuildPlan( 341 | this.planCompletion.text, 342 | this.fileList.split('\n')) 343 | console.log(this.buildPlan.items) 344 | await updateStatus() 345 | 346 | // Get contents of the files to modify. 347 | const planContext = this.buildPlan.readableString() 348 | const contentsContext = await this.buildPlan.readableContents( 349 | async (filePath: string) => { 350 | return await readFileFromContainer(container, `/root/${repositoryName}/${filePath}`) 351 | }) 352 | 353 | // Use ChatGPT to generate the build script. 354 | await this.getBranchCompletion(planContext, contentsContext) 355 | } 356 | 357 | // Generate the build script from the OpenAI completion. 358 | this.buildScript = applyCorrections(this.completion.text.trim()) 359 | await updateStatus() 360 | 361 | await writeFile(buildScriptPath, this.buildScript) 362 | await copyFileToContainer(container, buildScriptPath, containerHome) 363 | 364 | if (this.isBranch) { 365 | // Run the build script on a new branch, and push it to GitHub. 366 | await runScriptInContainer(container, 367 | scripts.CREATE_NEW_BRANCH + 368 | scripts.RUN_BUILD_SCRIPT + 369 | scripts.CD_GIT_ROOT + 370 | scripts.SETUP_GIT_CREDENTIALS + 371 | scripts.PUSH_BRANCH, 372 | parameters) 373 | } else { 374 | await runScriptInContainer(container, 375 | // Run the build script in an empty directory, and push the results to GitHub. 376 | scripts.SETUP_GIT_CONFIG + 377 | scripts.MAKE_PROJECT_DIR + 378 | scripts.RUN_BUILD_SCRIPT + 379 | scripts.CD_GIT_ROOT + 380 | scripts.SETUP_GIT_CREDENTIALS + 381 | scripts.PUSH_TO_REPO, 382 | parameters) 383 | } 384 | 385 | this.buildLog = await runScriptInContainer(container, 386 | scripts.GET_BUILD_LOG, 387 | parameters, true); 388 | 389 | await updateStatus({ finished: true }); 390 | 391 | if (debug) { 392 | // This is how we can debug the build script interactively. 393 | console.log("The container is still running!") 394 | console.log("To debug, run:") 395 | console.log("-----") 396 | console.log(`docker exec -it ${container.id} bash`) 397 | console.log("-----") 398 | // If we don't this, the process won't end because the container is running. 399 | process.exit() 400 | } else { 401 | // Stop and remove the container. 402 | await container.stop() 403 | console.log(`Container ${container.id} stopped.`) 404 | await container.remove() 405 | console.log(`Container ${container.id} removed.`) 406 | } 407 | } 408 | } 409 | } 410 | -------------------------------------------------------------------------------- /src/lib/buildPlan.ts: -------------------------------------------------------------------------------- 1 | import JSON5 from "json5" 2 | 3 | type BuildPlanItem = { 4 | filePath: string, 5 | action: "add" | "edit", 6 | description: string 7 | }; 8 | 9 | // A plan containing a list of files to edit or add in a repository. 10 | export class BuildPlan { 11 | items: BuildPlanItem[] = [] 12 | 13 | // Parse the output of a ChatGPT request into a BuildPlan object. 14 | constructor(inputText: string, files: string[]) { 15 | // Remove leading "./" from filenames. 16 | const fixPath = (file: string) => file.trim().replace(/^\.\//, ''); 17 | const fixedPaths = files.map(fixPath); 18 | console.log(fixedPaths) 19 | // Convert arrays into objects. 20 | this.items = JSON5.parse(inputText) 21 | .map(([filePath, action, description]: [string, string, string]) => { 22 | const fixedPath = fixPath(filePath); 23 | // Set to edit or new based on if the file exists in the repository. 24 | const exists = fixedPaths.includes(fixedPath); 25 | return { 26 | // Normalize filenames. 27 | filePath: fixedPath, 28 | action: exists ? "edit" : "add", 29 | description 30 | } 31 | }); 32 | return this; 33 | } 34 | 35 | // A string describing how each file will be changed. 36 | readableString = () => { 37 | const previewItemString = (item: BuildPlanItem) => `- ${item.action} ${item.filePath}: ${item.description}` 38 | return this.items.map(previewItemString).join("\n") 39 | } 40 | 41 | // A string containing the contents of each file that will be changed. 42 | readableContents = async (readFile: (input: string) => Promise) => { 43 | // Only include files that exist already. 44 | const existingFileItems = this.items.filter(item => item.action === "edit"); 45 | // Get the contents of each file as a string. 46 | const getFileContents = async ({ filePath }: BuildPlanItem) => { 47 | const fileContents = await readFile(filePath) 48 | const delimiter = "\n```\n" 49 | return `*${filePath}*:${delimiter}${fileContents}${delimiter}`; 50 | } 51 | const repositoryContents = await Promise.all( 52 | existingFileItems.map(getFileContents) 53 | ) 54 | return repositoryContents.join("\n") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["esnext", "dom"], 5 | "esModuleInterop": true, 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "outDir": "dist", 9 | "strict": true, 10 | "types": ["node"], 11 | "resolveJsonModule": true 12 | }, 13 | "include": ["*.ts", "src/lib/buildPlan.ts", "src/helpers/cli.ts", "src/helpers/container.ts", "src/helpers/corrections.ts", "src/helpers/github.ts", "src/helpers/openai.ts", "src/index.ts"] 14 | } 15 | --------------------------------------------------------------------------------