├── .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 |
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 | |
145 |
146 |
147 | Overview of the system and its parts
148 | |
149 |
150 |
151 |
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 | |
162 |
163 |
164 | Overview of the agentic process
165 | |
166 |
167 |
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 |
--------------------------------------------------------------------------------