Repository: smol-ai/developer
Branch: main
Commit: a6747d1a6ccd
Files: 48
Total size: 117.2 KB
Directory structure:
gitextract_dy16cnbs/
├── .devcontainer/
│ ├── devcontainer.json
│ └── dockerfile
├── .example.env
├── .gitignore
├── LICENSE
├── Makefile
├── dist/
│ └── smol_dev-0.0.3-py3-none-any.whl
├── examples/
│ └── v1_pong_game/
│ ├── ai.js
│ ├── index.html
│ ├── main.js
│ ├── shared_deps.md
│ └── style.css
├── main.py
├── prompt.md
├── pyproject.toml
├── readme.md
├── smol_dev/
│ ├── __init__.py
│ ├── api.py
│ ├── main.py
│ ├── prompts.py
│ └── utils.py
└── v0/
├── code2prompt/
│ ├── code2prompt-gpt3.md
│ └── code2prompt-gpt4.md
├── code2prompt.py
├── code2prompt2code/
│ ├── background.js
│ ├── content_script.js
│ ├── manifest.json
│ ├── popup.html
│ ├── popup.js
│ ├── shared_dependencies.md
│ └── styles.css
├── constants.py
├── debugger.py
├── debugger_no_modal.py
├── exampleChromeExtension/
│ ├── background.js
│ ├── content_script.js
│ ├── manifest.json
│ ├── popup.html
│ ├── popup.js
│ ├── prompt used for this extension.md
│ ├── shared_dependencies.md
│ └── styles.css
├── main.py
├── main_no_modal.py
├── readme.md
├── static/
│ └── readme.md
├── utils.py
└── v0_readme.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .devcontainer/devcontainer.json
================================================
{
"name": "Python Project", // The name of your dev container setup, can be anything you choose.
"dockerFile": "Dockerfile", // The path to the Dockerfile that describes your development environment.
"settings": {
"terminal.integrated.shell.linux": "/bin/bash", // Specifies the shell to be used in the integrated terminal in VS Code.
"python.pythonPath": "/usr/local/bin/python", // Specifies the path to the Python interpreter.
"python.linting.pylintEnabled": true, // Enables linting using pylint for Python files.
"python.linting.enabled": true // Enables linting for Python files in general.
},
"extensions": ["ms-python.python"], // Specifies VS Code extensions that should be installed in the dev container when it is created, in this case, the Microsoft Python extension.
"forwardPorts": [], // Specifies any ports that should be forwarded from the dev container to the host.
"postCreateCommand": "pip install -r requirements.txt" // Specifies a command or series of commands to run after the dev container is created.
}
================================================
FILE: .devcontainer/dockerfile
================================================
# syntax=docker/dockerfile:1 # Specifies Dockerfile version to use. In this case, version 1.
FROM python:3.9-slim-buster # Defines the base image to use for your Docker image. Here, it's the slim-buster version of the official Python 3.9 image.
WORKDIR /app # Sets the working directory in the Docker container. Any command that follows in the Dockerfile will be run in this directory.
COPY requirements.txt . # Copies the requirements.txt file from your project to the working directory in the Docker image.
RUN pip install -r requirements.txt # Runs pip install command in your Docker image to install Python dependencies listed in your requirements.txt file.
COPY . . # Copies everything else in your project (denoted by '.') to the working directory in the Docker image.
================================================
FILE: .example.env
================================================
OPENAI_API_KEY=sk-xxxxxx
================================================
FILE: .gitignore
================================================
.env
env
__pycache__
.vscode
.idea
/.idea/
venv/
# Ignore everything in the generated directory
/generated/*
# Don't ignore .gitkeep files in the generated directory
!/generated/.gitkeep
workspace
================================================
FILE: LICENSE
================================================
MIT License Copyright (c) 2023 swyx
Permission is hereby granted, free of
charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice
(including the next paragraph) shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: Makefile
================================================
.PHONY: clean build publish
build: clean
python -m pip install --upgrade --quiet setuptools wheel twine
python3 -m build
# python setup.py --quiet sdist bdist_wheel
publish: build
python -m twine check dist/*
# python -m twine upload dist/*
python3 -m twine upload dist/*
clean:
rm -r build dist *.egg-info || true
================================================
FILE: examples/v1_pong_game/ai.js
================================================
// Define AI's paddle speed
var aiSpeed = 2;
// Define AI's error rate
var aiError = 0.05;
/**
* Function to make a decision on the direction to move the AI paddle based on the ball's position and the error factor.
* @param {object} ball - The ball object
* @param {object} aiPaddle - The AI paddle object
*/
function aiDecision(ball, aiPaddle) {
// If ball is above the AI paddle and random number is greater than error rate, move the paddle up
if (ball.y < aiPaddle.y && Math.random() > aiError) {
aiPaddle.y -= aiSpeed;
}
// If ball is below the AI paddle and random number is greater than error rate, move the paddle down
else if (ball.y > aiPaddle.y && Math.random() > aiError) {
aiPaddle.y += aiSpeed;
}
}
================================================
FILE: examples/v1_pong_game/index.html
================================================
Pong Game
================================================
FILE: examples/v1_pong_game/main.js
================================================
// Define the canvas, paddles, ball and score
const canvas = document.getElementById("gameArea");
const ctx = canvas.getContext("2d");
let playerPaddle = { x: 0, y: 200, width: 10, height: 100 };
let aiPaddle = { x: 390, y: 200, width: 10, height: 100 };
let ball = { x: 200, y: 200, radius: 5, dx: 2, dy: 2 };
let score = { player: 0, ai: 0 };
// Initialize the game state
function startGame() {
playerPaddle.y = 200;
aiPaddle.y = 200;
ball.x = 200;
ball.y = 200;
ball.dx = 2;
ball.dy = 2;
score.player = 0;
score.ai = 0;
}
// Listen for mouse movement to control the player's paddle
canvas.addEventListener("mousemove", playerMove);
// Control the player's paddle based on mouse movement
function playerMove(event) {
let rect = canvas.getBoundingClientRect();
playerPaddle.y = event.clientY - rect.top - playerPaddle.height / 2;
}
// Control the AI paddle based on the ball's position
function aiMove() {
let targetY = ball.y - (aiPaddle.height - ball.radius) / 2;
aiPaddle.y += (targetY - aiPaddle.y) * 0.1;
}
// Check for collisions between the ball and the paddles or the boundaries of the canvas
function checkCollision() {
// Ball and player paddle
if (ball.y + ball.radius > playerPaddle.y && ball.y - ball.radius < playerPaddle.y + playerPaddle.height && ball.dx < 0) {
if (ball.x - ball.radius < playerPaddle.x + playerPaddle.width) {
ball.dx *= -1;
increaseBallSpeed();
}
}
// Ball and AI paddle
if (ball.y + ball.radius > aiPaddle.y && ball.y - ball.radius < aiPaddle.y + aiPaddle.height && ball.dx > 0) {
if (ball.x + ball.radius > aiPaddle.x) {
ball.dx *= -1;
increaseBallSpeed();
}
}
// Ball and top or bottom
if (ball.y + ball.radius > canvas.height || ball.y - ball.radius < 0) {
ball.dy *= -1;
}
}
// Update the score based on ball-paddle collisions
function updateScore() {
if (ball.x + ball.radius > canvas.width) {
score.player++;
startGame();
} else if (ball.x - ball.radius < 0) {
score.ai++;
startGame();
}
}
// Increase the ball's speed every time it bounces off a paddle
function increaseBallSpeed() {
ball.dx *= 1.1;
ball.dy *= 1.1;
}
// Update the game state at every frame
function updateGame() {
// Move the ball
ball.x += ball.dx;
ball.y += ball.dy;
// Move the AI paddle
aiMove();
// Check for collisions
checkCollision();
// Update the score
updateScore();
// Render the game state
drawGame();
// Schedule the next update
requestAnimationFrame(updateGame);
}
// Render the game state
function drawGame() {
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw the paddles
ctx.fillStyle = "yellow";
ctx.fillRect(playerPaddle.x, playerPaddle.y, playerPaddle.width, playerPaddle.height);
ctx.fillRect(aiPaddle.x, aiPaddle.y, aiPaddle.width, aiPaddle.height);
// Draw the ball
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2);
ctx.fillStyle = "red";
ctx.fill();
// Draw the score
ctx.font = "30px Arial";
ctx.fillText(score.player, 50, 50);
ctx.fillText(score.ai, canvas.width - 50, 50);
}
// Start the game
startGame();
updateGame();
================================================
FILE: examples/v1_pong_game/shared_deps.md
================================================
Here is a breakdown of the structure of the Pong Game app:
1. **index.html**: This is the main HTML file that creates the structure of the webpage. It includes a canvas for the game area, and two div elements for the player and AI paddles.
- *DOM Elements:*
- `id="gameArea"` for the canvas.
- `id="playerPaddle"` for the player's paddle.
- `id="aiPaddle"` for the AI's paddle.
2. **style.css**: This file contains the CSS styles for the canvas and paddles. It sets the canvas to a 400x400 black square and centers it in the page. The paddles are made 100px long and yellow, and the ball small and red.
3. **main.js**: This is the main JavaScript file that controls the functionality of the game. It includes functions to control the player's paddle following the mouse, the AI paddle following the ball, collision detection between the ball and the paddles, and scoring.
- *Variables*:
- `playerPaddle` and `aiPaddle` objects that represent the player and AI paddles.
- `ball` object represents the ball.
- `score` object keeps track of the player's and AI's scores.
- *Functions*:
- `startGame()`: Initializes the game state.
- `updateGame()`: Updates the game state at every frame, including moving the paddles and ball and checking for collisions.
- `drawGame()`: Renders the game state on the canvas.
- `playerMove(event)`: Controls the player's paddle based on mouse movement.
- `aiMove()`: Controls the AI paddle based on the ball's position.
- `checkCollision()`: Checks for collisions between the ball and the paddles or the boundaries of the canvas.
- `updateScore()`: Updates the score based on ball-paddle collisions.
- `increaseBallSpeed()`: Increases the ball's speed every time it bounces off a paddle.
4. **ai.js**: This file contains a simple AI algorithm to control the movement of the AI paddle. It slowly moves the paddle toward the ball at every frame, with some probability of error.
- *Variables*:
- `aiSpeed` determines the speed of the AI paddle.
- `aiError` determines the probability of error in the AI's movement.
- *Functions*:
- `aiDecision()`: Makes a decision on the direction to move the AI paddle based on the ball's position and the error factor.
All these files will be linked together in the `index.html` file. The JavaScript files are written in a manner that doesn't use the import/export keywords and only uses features supported by the Chrome browser to ensure compatibility. Each JavaScript function uses the DOM API to interact with the HTML elements based on their id names.
================================================
FILE: examples/v1_pong_game/style.css
================================================
/* CSS Styles for the Pong Game App */
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: black;
}
#gameArea {
background-color: black;
height: 400px;
width: 400px;
position: relative;
}
#playerPaddle, #aiPaddle {
position: absolute;
background-color: yellow;
width: 10px;
height: 100px;
}
#playerPaddle {
left: 0;
}
#aiPaddle {
right: 0;
}
.ball {
position: absolute;
background-color: red;
height: 10px;
width: 10px;
border-radius: 50%;
}
================================================
FILE: main.py
================================================
import sys
from smol_dev.prompts import plan, specify_file_paths, generate_code_sync
from smol_dev.utils import generate_folder, write_file
from smol_dev.main import main
import argparse
# for local testing
# python main.py --prompt "a simple JavaScript/HTML/CSS/Canvas app that is a one player game of PONG..." --generate_folder_path "generated" --debug True
if __name__ == "__main__":
prompt = """
a simple JavaScript/HTML/CSS/Canvas app that is a one player game of PONG.
The left paddle is controlled by the player, following where the mouse goes.
The right paddle is controlled by a simple AI algorithm, which slowly moves the paddle toward the ball at every frame, with some probability of error.
Make the canvas a 400 x 400 black square and center it in the app.
Make the paddles 100px long, yellow and the ball small and red.
Make sure to render the paddles and name them so they can controlled in javascript.
Implement the collision detection and scoring as well.
Every time the ball bouncess off a paddle, the ball should move faster.
It is meant to run in Chrome browser, so dont use anything that is not supported by Chrome, and don't use the import and export keywords.
"""
if len(sys.argv) == 2:
prompt = sys.argv[1]
args = None
else:
parser = argparse.ArgumentParser()
parser.add_argument("--prompt", type=str, help="Prompt for the app to be created.")
parser.add_argument("--model", type=str, default="gpt-4-0613", help="model to use. can also use gpt-3.5-turbo-0613")
parser.add_argument("--generate_folder_path", type=str, default="generated", help="Path of the folder for generated code.")
parser.add_argument("--debug", type=bool, default=False, help="Enable or disable debug mode.")
args = parser.parse_args()
if args.prompt:
prompt = args.prompt
# read file from prompt if it ends in a .md filetype
if len(prompt) < 100 and prompt.endswith(".md"):
with open(prompt, "r") as promptfile:
prompt = promptfile.read()
print(prompt)
if args is None:
# This is in case we're just calling the main function directly with a prompt
main(prompt=prompt)
else:
main(prompt=prompt, generate_folder_path=args.generate_folder_path, debug=args.debug, model=args.model)
================================================
FILE: prompt.md
================================================
a Chrome Manifest V3 extension that reads the current page, and offers a popup UI that has the page title+content and a textarea for a prompt (with a default value we specify). When the user hits submit, it sends the page title+content to the Anthropic Claude API along with the up to date prompt to summarize it. The user can modify that prompt and re-send the prompt+content to get another summary view of the content.
- Only when clicked:
- it injects a content script `content_script.js` on the currently open tab, and accesses the title `pageTitle` and main content (innerText) `pageContent` of the currently open page
(extracted via an injected content script, and sent over using a `storePageContent` action)
- in the background, receives the `storePageContent` data and stores it
- only once the new page content is stored, then it pops up a full height window with a minimalistic styled html popup
- in the popup script
- the popup should display a 10px tall rounded css animated red and white candy stripe loading indicator `loadingIndicator`, while waiting for the anthropic api to return
- with the currently fetching page title and a running timer in the center showing time elapsed since call started
- do not show it until the api call begins, and hide it when it ends.
- retrieves the page content data using a `getPageContent` action (and the background listens for the `getPageContent` action and retrieves that data) and displays the title at the top of the popup
- check extension storage for an `apiKey`, and if it isn't stored, asks for an API key to Anthropic Claude and stores it.
- at the bottom of the popup, show a vertically resizable form that has:
- a 2 line textarea with an id and label of `userPrompt`
- `userPrompt` has a default value of
```js
defaultPrompt = `Please provide a detailed, easy to read HTML summary of the given content`;
```js
- a 4 line textarea with an id and label of `stylePrompt`
- `stylePrompt` has a default value of
```js
defaultStyle = `Respond with 3-4 highlights per section with important keywords, people, numbers, and facts bolded in this HTML format:
{title here}
{section title here}
{summary of the section with important keywords, people, numbers, and facts bolded and key quotes repeated}
{first point}: {short explanation with important keywords, people, numbers, and facts bolded}
{second point}: {same as above}
{third point}: {same as above}
{second section here}
{summary of the section with important keywords, people, numbers, and facts bolded and key quotes repeated}
{summary of the section with important keywords, people, numbers, and facts bolded and key quotes repeated}
{third section here}
With all the words in brackets replaced by the summary of the content. sanitize non visual HTML tags with HTML entities, so becomes <template> but stays the same. Only draw from the source content, do not hallucinate. Finally, end with other questions that the user might want answered based on this source content:
Next prompts
{question 1}
{question 2}
{question 3}
`;
```js
- and in the last row, on either side,
- and a nicely styled submit button with an id of `sendButton` (tactile styling that "depresses" on click)
- only when `sendButton` is clicked, calls the Anthropic model endpoint https://api.anthropic.com/v1/complete with:
- append the page title
- append the page content
- add the prompt which is a concatenation of
```js
finalPrompt = `Human: ${userPrompt} \n\n ${stylePrompt} \n\n Assistant:`
```
- and use the `claude-instant-v1` model (if `pageContent` is <70k words) or the `claude-instant-v1-100k` model (if more)
- requesting max tokens = the higher of (25% of the length of the page content, or 750 words)
- if another submit event is hit while the previous api call is still inflight, cancel that and start the new one
- renders the Anthropic-generated result at the top of the popup in a div with an id of `content`
Important Details:
- It has to run in a browser environment, so no Nodejs APIs allowed.
- the return signature of the anthropic api is curl https://api.anthropic.com/v1/complete\
-H "x-api-key: $API_KEY"\
-H 'content-type: application/json'\
-d '{
"prompt": "\n\nHuman: Tell me a haiku about trees\n\nAssistant: ",
"model": "claude-v1", "max_tokens_to_sample": 1000, "stop_sequences": ["\n\nHuman:"]
}'
{"completion":" Here is a haiku about trees:\n\nSilent sentinels, \nStanding solemn in the woods,\nBranches reaching sky.","stop":"\n\nHuman:","stop_reason":"stop_sequence","truncated":false,"log_id":"f5d95cf326a4ac39ee36a35f434a59d5","model":"claude-v1","exception":null}
- in the string prompt sent to Anthropic, first include the page title and page content, and finally append the prompt, clearly vertically separated by spacing.
- if the Anthropic api call is a 401, handle that by clearing the stored anthropic api key and asking for it again.
- add styles to make sure the popup's styling follows the basic rules of web design, for example having margins around the body, and a system font stack.
- style the popup body with but insist on body margins of 16 and a minimum width of 400 and height of 600.
## debugging notes
inside of background.js, just take the getPageContent response directly
```js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'storePageContent') {
// dont access request.pageContent
chrome.storage.local.set({ pageContent: request }, () => {
sendResponse({ success: true });
});
} else if (request.action === 'getPageContent') {
chrome.storage.local.get(['pageContent'], (result) => {
// dont access request.pageContent
sendResponse(result);
});
}
return true;
});
```
inside of popup.js, Update the function calls to `requestAnthropicSummary`
in `popup.js` to pass the `apiKey`:
```javascript
chrome.storage.local.get(['apiKey'], (result) => {
const apiKey = result.apiKey;
requestAnthropicSummary(defaultPrompt, apiKey);
});
sendButton.addEventListener('click', () => {
chrome.storage.local.get(['apiKey'], (result) => {
const apiKey = result.apiKey;
requestAnthropicSummary(userPrompt.value, apiKey);
});
});
```
in `popup.js`, store the defaultPrompt at the top level.
also, give a HTML format to the anthropic prompt
================================================
FILE: pyproject.toml
================================================
[tool.poetry]
name = "smol_dev"
version = "0.0.3"
description = "python module of smol developer"
authors = ["swyx "]
license = "MIT"
readme = "readme.md"
packages = [{ include = "smol_dev" }]
[tool.poetry.dependencies]
python = ">=3.10,<4.0.0"
openai = "^0.27.8"
openai-function-call = "^0.0.5"
tenacity = "^8.2.2"
agent-protocol = "^1.0.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
src = "src.__main__:main"
api = "smol_dev.api:main"
[project.urls]
"Homepage" = "https://github.com/smol-ai/developer"
"Bug Tracker" = "https://github.com/smol-ai/developer/issues"
================================================
FILE: readme.md
================================================
# 🐣 smol developer
Morph
***Human-centric & Coherent Whole Program Synthesis*** aka your own personal junior developer
> [Build the thing that builds the thing!](https://twitter.com/swyx/status/1657578738345979905) a `smol dev` for every dev in every situation
This is a "junior developer" agent (aka `smol dev`) that either:
1. scaffolds an entire codebase out for you once you give it a product spec
2. gives you basic building blocks to have a smol developer inside of your own app.
Instead of making and maintaining specific, rigid, one-shot starters, like `create-react-app`, or `create-nextjs-app`, this is basically is or helps you make [`create-anything-app`](https://news.ycombinator.com/item?id=35942352) where you develop your scaffolding prompt in a tight loop with your smol dev.
After the [successful initial v0 launch](https://twitter.com/swyx/status/1657578738345979905), smol developer was rewritten to be **even smol-ler**, and importable from a library!
## Basic Usage
### In Git Repo mode
```bash
# install
git clone https://github.com/smol-ai/developer.git
cd developer
poetry install # install dependencies. pip install poetry if you need
# run
python main.py "a HTML/JS/CSS Tic Tac Toe Game" # defaults to gpt-4-0613
# python main.py "a HTML/JS/CSS Tic Tac Toe Game" --model=gpt-3.5-turbo-0613
# other cli flags
python main.py --prompt prompt.md # for longer prompts, move them into a markdown file
python main.py --prompt prompt.md --debug True # for debugging
```
This lets you develop apps as a human in the loop, as per the original version of smol developer.
*engineering with prompts, rather than prompt engineering*
The demo example in `prompt.md` shows the potential of AI-enabled, but still firmly human developer centric, workflow:
- Human writes a basic prompt for the app they want to build
- `main.py` generates code
- Human runs/reads the code
- Human can:
- simply add to the prompt as they discover underspecified parts of the prompt
- manually runs the code and identifies errors
- *paste the error into the prompt* just like they would file a GitHub issue
- for extra help, they can use `debugger.py` which reads the whole codebase to make specific code change suggestions
Loop until happiness is attained. Notice that AI is only used as long as it is adding value - once it gets in your way, just take over the codebase from your smol junior developer with no fuss and no hurt feelings. (*we could also have smol-dev take over an existing codebase and bootstrap its own prompt... but that's a Future Direction*)
In this way you can use your clone of this repo itself to prototype/develop your app.
### In Library mode
This is the new thing in smol developer v1! Add `smol developer` to your own projects!
```bash
pip install smol_dev
```
Here you can basically look at the contents of `main.py` as our "documentation" of how you can use these functions and prompts in your own app:
```python
from smol_dev.prompts import plan, specify_file_paths, generate_code_sync
prompt = "a HTML/JS/CSS Tic Tac Toe Game"
shared_deps = plan(prompt) # returns a long string representing the coding plan
# do something with the shared_deps plan if you wish, for example ask for user confirmation/edits and iterate in a loop
file_paths = specify_file_paths(prompt, shared_deps) # returns an array of strings representing the filenames it needs to write based on your prompt and shared_deps. Relies on OpenAI's new Function Calling API to guarantee JSON.
# do something with the filepaths if you wish, for example display a plan
# loop through file_paths array and generate code for each file
for file_path in file_paths:
code = generate_code_sync(prompt, shared_deps, file_path) # generates the source code of each file
# do something with the source code of the file, eg. write to disk or display in UI
# there is also an async `generate_code()` version of this
```
### In API mode (via [Agent Protocol](https://github.com/e2b-dev/agent-protocol))
To start the server run:
```bash
poetry run api
```
or
```bash
python smol_dev/api.py
```
and then you can call the API using either the following commands:
To **create a task** run:
```bash
curl --request POST \
--url http://localhost:8000/agent/tasks \
--header 'Content-Type: application/json' \
--data '{
"input": "Write simple script in Python. It should write '\''Hello world!'\'' to hi.txt"
}'
```
You will get a response like this:
```json
{"input":"Write simple script in Python. It should write 'Hello world!' to hi.txt","task_id":"d2c4e543-ae08-4a97-9ac5-5f9a4459cb19","artifacts":[]}
```
Then to **execute one step of the task** copy the `task_id` you got from the previous request and run:
```bash
curl --request POST \
--url http://localhost:8000/agent/tasks//steps
```
or you can use [Python client library](https://github.com/e2b-dev/agent-protocol/tree/main/agent_client/python):
```python
from agent_protocol_client import AgentApi, ApiClient, TaskRequestBody
...
prompt = "Write simple script in Python. It should write 'Hello world!' to hi.txt"
async with ApiClient() as api_client:
# Create an instance of the API class
api_instance = AgentApi(api_client)
task_request_body = TaskRequestBody(input=prompt)
task = await api_instance.create_agent_task(
task_request_body=task_request_body
)
task_id = task.task_id
response = await api_instance.execute_agent_task_step(task_id=task_id)
...
```
## examples/prompt gallery
- [6 minute video demo](https://youtu.be/UCo7YeTy-aE) - (sorry for sped up audio, we were optimizing for twitter, bad call)
- this was the original smol developer demo - going from prompt to full chrome extension that requests and stores and apikey, generates a popup window, reads and transmits page content, and usefully summarizes any website with Anthropic Claude, switching models up to the 100k one based on length of input
- the prompt is located in [prompt.md](https://github.com/smol-ai/developer/blob/main/prompt.md) and it outputs [/exampleChromeExtension](https://github.com/smol-ai/developer/tree/main/examples/exampleChromeExtension)
- `smol-plugin` - prompt to ChatGPT plugin ([tweet](https://twitter.com/ultrasoundchad/status/1659366507409985536?s=20), [fork](https://github.com/gmchad/smol-plugin))
- [Prompt to Pokemon App](https://twitter.com/RobertCaracaus/status/1659312419485761536?s=20)
- [Political Campaign CRM Program example](https://github.com/smol-ai/developer/pull/22/files)
- [Lessons from Creating a VSCode Extension with GPT-4](https://bit.kevinslin.com/p/leveraging-gpt-4-to-automate-the) (also on [HN](https://news.ycombinator.com/item?id=36071342))
- [7 min Video: Smol AI Developer - Build ENTIRE Codebases With A Single Prompt](https://www.youtube.com/watch?v=DzRoYc2UGKI) produces a full working OpenAI CLI python app from a prompt
- [12 min Video: SMOL AI - Develop Large Scale Apps with AGI in one click](https://www.youtube.com/watch?v=zsxyqz6SYp8) scaffolds a surprisingly complex React/Node/MongoDB full stack app in 40 minutes and $9
I'm actively seeking more examples, please PR yours!
sorry for the lack of examples, I know that is frustrating but I wasnt ready for so many of you lol
## major forks/alternatives
please send in alternative implementations, and deploy strategies on alternative stacks!
- **JS/TS**: https://github.com/PicoCreator/smol-dev-js A pure JS variant of smol-dev, allowing even smoler incremental changes via prompting (if you dun want to do the whole spec2code thing), allowing you to plug it into any project live (for better or worse)
- **C#/Dotnet**: https://github.com/colhountech/smol-ai-dotnet in C#!
- **Golang**: https://github.com/tmc/smol-dev-go in Go
- https://github.com/gmchad/smol-plugin automatically generate @openai plugins by specifying your API in markdown in smol-developer style
- your fork here!
### innovations and insights
> Please subscribe to https://latent.space/ for a fuller writeup and insights and reflections
- **Markdown is all you need** - Markdown is the perfect way to prompt for whole program synthesis because it is easy to mix english and code (whether `variable_names` or entire \`\`\` code fenced code samples)
- turns out you can specify prompts in code in prompts and gpt4 obeys that to the letter
- **Copy and paste programming**
- teaching the program to understand how to code around a new API (Anthropic's API is after GPT3's knowledge cutoff) by just pasting in the `curl` input and output
- pasting error messages into the prompt and vaguely telling the program how you'd like it handled. it kind of feels like "logbook driven programming".
- **Debugging by `cat`ing** the whole codebase with your error message and getting specific fix suggestions - particularly delightful!
- **Tricks for whole program coherence** - our chosen example usecase, Chrome extensions, have a lot of indirect dependencies across files. Any hallucination of cross dependencies causes the whole program to error.
- We solved this by adding an intermediate step asking GPT to think through `shared_dependencies.md`, and then insisting on using that in generating each file. This basically means GPT is able to talk to itself...
- ... but it's not perfect, yet. `shared_dependencies.md` is sometimes not comperehensive in understanding what are hard dependencies between files. So we just solved it by specifying a specific `name` in the prompt. felt dirty at first but it works, and really it's just clear unambiguous communication at the end of the day.
- see `prompt.md` for SOTA smol-dev prompting
- **Low activation energy for unfamiliar APIs**
- we have never really learned css animations, but now can just say we want a "juicy css animated red and white candy stripe loading indicator" and it does the thing.
- ditto for Chrome Extension Manifest v3 - the docs are an abject mess, but fortunately we don't have to read them now to just get a basic thing done
- the Anthropic docs (bad bad) were missing guidance on what return signature they have. so just curl it and dump it in the prompt lol.
- **Modal is all you need** - we chose Modal to solve 4 things:
- solve python dependency hell in dev and prod
- parallelizable code generation
- simple upgrade path from local dev to cloud hosted endpoints (in future)
- fault tolerant openai api calls with retries/backoff, and attached storage (for future use)
> Please subscribe to https://latent.space/ for a fuller writeup and insights and reflections
### caveats
We were working on a Chrome Extension, which requires images to be generated, so we added some usecase specific code in there to skip destroying/regenerating them, that we haven't decided how to generalize.
We dont have access to GPT4-32k, but if we did, we'd explore dumping entire API/SDK documentation into context.
The feedback loop is very slow right now (`time` says about 2-4 mins to generate a program with GPT4, even with parallelization due to Modal (occasionally spiking higher)), but it's a safe bet that it will go down over time (see also "future directions" below).
## future directions
things to try/would accept open issue discussions and PRs:
- **specify .md files for each generated file**, with further prompts that could finetune the output in each of them
- so basically like `popup.html.md` and `content_script.js.md` and so on
- **bootstrap the `prompt.md`** for existing codebases - write a script to read in a codebase and write a descriptive, bullet pointed prompt that generates it
- done by `smol pm`, but its not very good yet - would love for some focused polish/effort until we have quine smol developer that can generate itself lmao
- **ability to install its own dependencies**
- this leaks into depending on the execution environment, which we all know is the path to dependency madness. how to avoid? dockerize? nix? [web container](https://twitter.com/litbid/status/1658154530385670150)?
- Modal has an interesting possibility: generate functions that speak modal which also solves the dependency thing https://twitter.com/akshat_b/status/1658146096902811657
- **self-heal** by running the code itself and use errors as information for reprompting
- however its a bit hard to get errors from the chrome extension environment so we did not try this
- **using anthropic as the coding layer**
- you can run `modal run anthropic.py --prompt prompt.md --outputdir=anthropic` to try it
- but it doesnt work because anthropic doesnt follow instructions to generate file code very well.
- **make agents that autonomously run this code in a loop/watch the prompt file** and regenerate code each time, on a new git branch
- the code could be generated on 5 simultaneous git branches and checking their output would just involve switching git branches
================================================
FILE: smol_dev/__init__.py
================================================
from smol_dev.prompts import *
__author__ = "morph"
================================================
FILE: smol_dev/api.py
================================================
import enum
import os
from pathlib import Path
from smol_dev.prompts import plan, specify_file_paths, generate_code
from smol_dev.utils import write_file
from agent_protocol import Agent, Step, Task
class StepTypes(str, enum.Enum):
PLAN = "plan"
SPECIFY_FILE_PATHS = "specify_file_paths"
GENERATE_CODE = "generate_code"
async def _generate_shared_deps(step: Step) -> Step:
task = await Agent.db.get_task(step.task_id)
shared_deps = plan(task.input)
await Agent.db.create_step(
step.task_id,
StepTypes.SPECIFY_FILE_PATHS,
additional_properties={
"shared_deps": shared_deps,
},
)
step.output = shared_deps
return step
async def _generate_file_paths(task: Task, step: Step) -> Step:
shared_deps = step.additional_properties["shared_deps"]
file_paths = specify_file_paths(task.input, shared_deps)
for file_path in file_paths[:-1]:
await Agent.db.create_step(
task.task_id,
f"Generate code for {file_path}",
additional_properties={
"shared_deps": shared_deps,
"file_path": file_paths[-1],
},
)
await Agent.db.create_step(
task.task_id,
f"Generate code for {file_paths[-1]}",
is_last=True,
additional_properties={
"shared_deps": shared_deps,
"file_path": file_paths[-1],
},
)
step.output = f"File paths are: {str(file_paths)}"
return step
async def _generate_code(task: Task, step: Step) -> Step:
shared_deps = step.additional_properties["shared_deps"]
file_path = step.additional_properties["file_path"]
code = await generate_code(task.input, shared_deps, file_path)
step.output = code
write_file(os.path.join(Agent.get_workspace(task.task_id), file_path), code)
path = Path("./" + file_path)
await Agent.db.create_artifact(
task_id=task.task_id,
step_id=step.step_id,
relative_path=str(path.parent),
file_name=path.name,
)
return step
async def task_handler(task: Task) -> None:
if not task.input:
raise Exception("No task prompt")
await Agent.db.create_step(task.task_id, StepTypes.PLAN)
async def step_handler(step: Step):
task = await Agent.db.get_task(step.task_id)
if step.name == StepTypes.PLAN:
return await _generate_shared_deps(step)
elif step.name == StepTypes.SPECIFY_FILE_PATHS:
return await _generate_file_paths(task, step)
else:
return await _generate_code(task, step)
Agent.setup_agent(task_handler, step_handler).start()
================================================
FILE: smol_dev/main.py
================================================
import sys
import time
from smol_dev.prompts import plan, specify_file_paths, generate_code_sync
from smol_dev.utils import generate_folder, write_file
import argparse
# model = "gpt-3.5-turbo-0613"
defaultmodel = "gpt-4-0613"
def main(prompt, generate_folder_path="generated", debug=False, model: str = defaultmodel):
# create generateFolder folder if doesnt exist
generate_folder(generate_folder_path)
# plan shared_deps
if debug:
print("--------shared_deps---------")
with open(f"{generate_folder_path}/shared_deps.md", "wb") as f:
start_time = time.time()
def stream_handler(chunk):
f.write(chunk)
if debug:
end_time = time.time()
sys.stdout.write("\r \033[93mChars streamed\033[0m: {}. \033[93mChars per second\033[0m: {:.2f}".format(stream_handler.count, stream_handler.count / (end_time - start_time)))
sys.stdout.flush()
stream_handler.count += len(chunk)
stream_handler.count = 0
stream_handler.onComplete = lambda x: sys.stdout.write("\033[0m\n") # remove the stdout line when streaming is complete
shared_deps = plan(prompt, stream_handler, model=model)
if debug:
print(shared_deps)
write_file(f"{generate_folder_path}/shared_deps.md", shared_deps)
if debug:
print("--------shared_deps---------")
# specify file_paths
if debug:
print("--------specify_filePaths---------")
file_paths = specify_file_paths(prompt, shared_deps, model=model)
if debug:
print(file_paths)
if debug:
print("--------file_paths---------")
# loop through file_paths array and generate code for each file
for file_path in file_paths:
file_path = f"{generate_folder_path}/{file_path}" # just append prefix
if debug:
print(f"--------generate_code: {file_path} ---------")
start_time = time.time()
def stream_handler(chunk):
if debug:
end_time = time.time()
sys.stdout.write("\r \033[93mChars streamed\033[0m: {}. \033[93mChars per second\033[0m: {:.2f}".format(stream_handler.count, stream_handler.count / (end_time - start_time)))
sys.stdout.flush()
stream_handler.count += len(chunk)
stream_handler.count = 0
stream_handler.onComplete = lambda x: sys.stdout.write("\033[0m\n") # remove the stdout line when streaming is complete
code = generate_code_sync(prompt, shared_deps, file_path, stream_handler, model=model)
if debug:
print(code)
if debug:
print(f"--------generate_code: {file_path} ---------")
# create file with code content
write_file(file_path, code)
print("--------smol dev done!---------")
# for local testing
# python main.py --prompt "a simple JavaScript/HTML/CSS/Canvas app that is a one player game of PONG..." --generate_folder_path "generated" --debug True
if __name__ == "__main__":
prompt = """
a simple JavaScript/HTML/CSS/Canvas app that is a one player game of PONG.
The left paddle is controlled by the player, following where the mouse goes.
The right paddle is controlled by a simple AI algorithm, which slowly moves the paddle toward the ball at every frame, with some probability of error.
Make the canvas a 400 x 400 black square and center it in the app.
Make the paddles 100px long, yellow and the ball small and red.
Make sure to render the paddles and name them so they can controlled in javascript.
Implement the collision detection and scoring as well.
Every time the ball bouncess off a paddle, the ball should move faster.
It is meant to run in Chrome browser, so dont use anything that is not supported by Chrome, and don't use the import and export keywords.
"""
if len(sys.argv) == 2:
prompt = sys.argv[1]
else:
parser = argparse.ArgumentParser()
parser.add_argument("--prompt", type=str, required=True, help="Prompt for the app to be created.")
parser.add_argument("--generate_folder_path", type=str, default="generated", help="Path of the folder for generated code.")
parser.add_argument("--debug", type=bool, default=False, help="Enable or disable debug mode.")
args = parser.parse_args()
if args.prompt:
prompt = args.prompt
print(prompt)
main(prompt=prompt, generate_folder_path=args.generate_folder_path, debug=args.debug)
================================================
FILE: smol_dev/prompts.py
================================================
import asyncio
import re
import time
from typing import List, Optional, Callable, Any
import openai
from openai_function_call import openai_function
from tenacity import (
retry,
stop_after_attempt,
wait_random_exponential,
)
import logging
logger = logging.getLogger(__name__)
SMOL_DEV_SYSTEM_PROMPT = """
You are a top tier AI developer who is trying to write a program that will generate code for the user based on their intent.
Do not leave any todos, fully implement every feature requested.
When writing code, add comments to explain what you intend to do and why it aligns with the program plan and specific instructions from the original prompt.
"""
@openai_function
def file_paths(files_to_edit: List[str]) -> List[str]:
"""
Construct a list of strings.
"""
# print("filesToEdit", files_to_edit)
return files_to_edit
def specify_file_paths(prompt: str, plan: str, model: str = 'gpt-3.5-turbo-0613'):
completion = openai.ChatCompletion.create(
model=model,
temperature=0.7,
functions=[file_paths.openai_schema],
function_call={"name": "file_paths"},
messages=[
{
"role": "system",
"content": f"""{SMOL_DEV_SYSTEM_PROMPT}
Given the prompt and the plan, return a list of strings corresponding to the new files that will be generated.
""",
},
{
"role": "user",
"content": f""" I want a: {prompt} """,
},
{
"role": "user",
"content": f""" The plan we have agreed on is: {plan} """,
},
],
)
result = file_paths.from_response(completion)
return result
def plan(prompt: str, stream_handler: Optional[Callable[[bytes], None]] = None, model: str='gpt-3.5-turbo-0613', extra_messages: List[Any] = []):
completion = openai.ChatCompletion.create(
model=model,
temperature=0.7,
stream=True,
messages=[
{
"role": "system",
"content": f"""{SMOL_DEV_SYSTEM_PROMPT}
In response to the user's prompt, write a plan using GitHub Markdown syntax. Begin with a YAML description of the new files that will be created.
In this plan, please name and briefly describe the structure of code that will be generated, including, for each file we are generating, what variables they export, data schemas, id names of every DOM elements that javascript functions will use, message names, and function names.
Respond only with plans following the above schema.
""",
},
{
"role": "user",
"content": f""" the app prompt is: {prompt} """,
},
*extra_messages,
],
)
collected_messages = []
for chunk in completion:
chunk_message_dict = chunk["choices"][0]
chunk_message = chunk_message_dict["delta"] # extract the message
if chunk_message_dict["finish_reason"] is None:
collected_messages.append(chunk_message) # save the message
if stream_handler:
try:
stream_handler(chunk_message["content"].encode("utf-8"))
except Exception as err:
logger.info("\nstream_handler error:", err)
logger.info(chunk_message)
# if stream_handler and hasattr(stream_handler, "onComplete"): stream_handler.onComplete('done')
full_reply_content = "".join([m.get("content", "") for m in collected_messages])
return full_reply_content
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
async def generate_code(prompt: str, plan: str, current_file: str, stream_handler: Optional[Callable[Any, Any]] = None,
model: str = 'gpt-3.5-turbo-0613') -> str:
first = True
chunk_count = 0
start_time = time.time()
completion = openai.ChatCompletion.acreate(
model=model,
temperature=0.7,
messages=[
{
"role": "system",
"content": f"""{SMOL_DEV_SYSTEM_PROMPT}
In response to the user's prompt,
Please name and briefly describe the structure of the app we will generate, including, for each file we are generating, what variables they export, data schemas, id names of every DOM elements that javascript functions will use, message names, and function names.
We have broken up the program into per-file generation.
Now your job is to generate only the code for the file: {current_file}
only write valid code for the given filepath and file type, and return only the code.
do not add any other explanation, only return valid code for that file type.
""",
},
{
"role": "user",
"content": f""" the plan we have agreed on is: {plan} """,
},
{
"role": "user",
"content": f""" the app prompt is: {prompt} """,
},
{
"role": "user",
"content": f"""
Make sure to have consistent filenames if you reference other files we are also generating.
Remember that you must obey 3 things:
- you are generating code for the file {current_file}
- do not stray from the names of the files and the plan we have decided on
- MOST IMPORTANT OF ALL - every line of code you generate must be valid code. Do not include code fences in your response, for example
Bad response (because it contains the code fence):
```javascript
console.log("hello world")
```
Good response (because it only contains the code):
console.log("hello world")
Begin generating the code now.
""",
},
],
stream=True,
)
collected_messages = []
async for chunk in await completion:
chunk_message_dict = chunk["choices"][0]
chunk_message = chunk_message_dict["delta"] # extract the message
if chunk_message_dict["finish_reason"] is None:
collected_messages.append(chunk_message) # save the message
if stream_handler:
try:
stream_handler(chunk_message["content"].encode("utf-8"))
except Exception as err:
logger.info("\nstream_handler error:", err)
logger.info(chunk_message)
# if stream_handler and hasattr(stream_handler, "onComplete"): stream_handler.onComplete('done')
code_file = "".join([m.get("content", "") for m in collected_messages])
pattern = r"```[\w\s]*\n([\s\S]*?)```" # codeblocks at start of the string, less eager
code_blocks = re.findall(pattern, code_file, re.MULTILINE)
return code_blocks[0] if code_blocks else code_file
def generate_code_sync(prompt: str, plan: str, current_file: str,
stream_handler: Optional[Callable[Any, Any]] = None,
model: str = 'gpt-3.5-turbo-0613') -> str:
loop = asyncio.get_event_loop()
return loop.run_until_complete(generate_code(prompt, plan, current_file, stream_handler, model))
================================================
FILE: smol_dev/utils.py
================================================
import os
import shutil
def generate_folder(folder_path: str):
if not os.path.exists(folder_path):
os.makedirs(folder_path)
else:
shutil.rmtree(folder_path)
os.makedirs(folder_path)
def write_file(file_path: str, content: str):
# if filepath doesn't exist, create it
if not os.path.exists(os.path.dirname(file_path)):
os.makedirs(os.path.dirname(file_path))
with open(file_path, "w") as f:
f.write(content)
================================================
FILE: v0/code2prompt/code2prompt-gpt3.md
================================================
The program is a Chrome extension that summarizes web pages using the Anthropic Claude API. It consists of several files: popup.js, styles.css,
background.js, popup.html, shared_dependencies.md, and content_script.js.
popup.html is the main user interface for the extension, containing a form with several input fields and a submit button. popup.js handles the logic
for the form, including retrieving the user's input, calling the Anthropic API, and rendering the summary in the content div. styles.css provides
styling for the UI elements.
background.js is responsible for executing content_script.js, which retrieves the page content (title and body text) and sends it to popup.js for
processing. It also handles storing and retrieving the page content data using Chrome's storage API.
shared_dependencies.md lists the shared variables, data schemas, DOM element IDs, message names, and function names used across the various files.
Overall, the program uses a combination of JavaScript, HTML, and CSS to provide a user-friendly interface for summarizing web pages using the
Anthropic Claude API.
================================================
FILE: v0/code2prompt/code2prompt-gpt4.md
================================================
# Anthropic Claude Summary Extension
A Chrome extension that summarizes web pages using the
Anthropic Claude API.
## Files
### popup.js
This file contains the main logic for the popup window of the
extension. It listens for the DOMContentLoaded event and
initializes the following DOM elements:
- `userPrompt`: A textarea for the user to input a prompt.
- `stylePrompt`: A textarea for the user to input a style
prompt.
- `maxTokens`: An input field for the user to set the maximum
number of tokens for the summary.
- `sendButton`: A button to submit the form and request a
summary.
- `content`: A div to display the summary.
- `loadingIndicator`: A div to display a loading indicator
while waiting for the summary.
The `sendButton` has a click event listener that calls the
`requestAnthropicSummary` function. This function sends a
message to the background script to get the page content,
constructs a prompt using the user input and page content,
and sends a POST request to the Anthropic Claude API. The
response is then displayed in the `content` div.
### styles.css
This file contains the CSS styles for the popup window. It
defines styles for the body, textarea, input, button,
content, and loadingIndicator elements.
### background.js
This file contains the background script for the extension.
It listens for the following events:
- `chrome.action.onClicked`: Executes the content_script.js
file on the active tab when the extension icon is clicked.
- `chrome.runtime.onMessage`: Listens for messages with the
following actions:
- `storePageContent`: Stores the page title and content in
the local storage using the tab ID as the key.
- `getPageContent`: Retrieves the page title and content
from the local storage using the tab ID and sends it as a
response.
### popup.html
This file contains the HTML structure for the popup window.
It includes the following elements:
- A form with labels and inputs for userPrompt, stylePrompt,
and maxTokens.
- A submit button with the ID "sendButton".
- A div with the ID "content" to display the summary.
- A div with the ID "loadingIndicator" to display a loading
indicator.
The file also includes a link to the styles.css file and a
script tag for the popup.js file.
### shared_dependencies.md
This file lists the shared dependencies, variables, DOM
element IDs, message names, and function names used in the
extension.
### content_script.js
This file contains a function called `storePageContent` that
retrieves the page title and content and sends a message to
the background script with the action "storePageContent" and
the data as an object containing the title and content.
It also listens for messages with the action
"storePageContent" and calls the `storePageContent` function
when received.
### manifest.json
This file contains the manifest for the extension. It
includes the following properties:
- `manifest_version`: Set to 2.
- `name`: Set to "Anthropic Claude Summary Extension".
- `version`: Set to "1.0".
- `description`: Set to "A Chrome extension that summarizes
web pages using the Anthropic Claude API."
- `permissions`: Includes "activeTab" and "storage".
- `action`: Defines the default_popup, and default_icon for
the extension.
- `background`: Includes the background.js script and sets
the "persistent" property to false.
- `content_scripts`: Includes the content_script.js file and
matches all URLs.
- `icons`: Defines the icons for the extension.
**Note**: Ensure that all the IDs of the DOM elements and the
data structure of `pageContent` referenced/shared by the
JavaScript files match up exactly. Use only Chrome Manifest
V3 APIs. Rename the extension to "code2prompt2code".
================================================
FILE: v0/code2prompt.py
================================================
import modal
import os
from constants import DEFAULT_DIR, DEFAULT_MODEL, DEFAULT_MAX_TOKENS, EXTENSION_TO_SKIP
stub = modal.Stub("smol-codetoprompt-v1")
openai_image = modal.Image.debian_slim().pip_install("openai")
def read_file(filename):
with open(filename, 'r') as file:
return file.read()
def walk_directory(directory):
code_contents = {}
for root, dirs, files in os.walk(directory):
for file in files:
if not any(file.endswith(ext) for ext in EXTENSION_TO_SKIP):
try:
relative_filepath = os.path.relpath(os.path.join(root, file), directory)
code_contents[relative_filepath] = read_file(os.path.join(root, file))
except Exception as e:
code_contents[relative_filepath] = f"Error reading file {file}: {str(e)}"
return code_contents
@stub.local_entrypoint()
def main(prompt=None, directory=DEFAULT_DIR, model=DEFAULT_MODEL):
code_contents = walk_directory(directory)
# Now, `code_contents` is a dictionary that contains the content of all your non-image files
# You can send this to OpenAI's text-davinci-003 for help
context = "\n".join(f"{path}:\n{contents}" for path, contents in code_contents.items())
system = "You are an AI debugger who is trying to fully describe a program, in order for another AI program to reconstruct every file, data structure, function and functionality. The user has provided you with the following files and their contents:"
prompt = "My files are as follows: " + context + "\n\n" + (("Take special note of the following: " + prompt) if prompt else "")
prompt += "\n\nDescribe the program in markdown using specific language that will help another AI program reconstruct the given program in as high fidelity as possible."
res = generate_response.call(system, prompt, model)
# print res in teal
print("\033[96m" + res + "\033[0m")
@stub.function(
image=openai_image,
secret=modal.Secret.from_dotenv(),
retries=modal.Retries(
max_retries=3,
backoff_coefficient=2.0,
initial_delay=1.0,
),
concurrency_limit=5,
timeout=120,
)
def generate_response(system_prompt, user_prompt, model=DEFAULT_MODEL, *args):
import openai
# Set up your OpenAI API credentials
openai.api_key = os.environ["OPENAI_API_KEY"]
messages = []
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": user_prompt})
# loop thru each arg and add it to messages alternating role between "assistant" and "user"
role = "assistant"
for value in args:
messages.append({"role": role, "content": value})
role = "user" if role == "assistant" else "assistant"
params = {
'model': model,
"messages": messages,
"max_tokens": 2500,
"temperature": 0,
}
# Send the API request
response = openai.ChatCompletion.create(**params)
# Get the reply from the API response
reply = response.choices[0]["message"]["content"]
return reply
================================================
FILE: v0/code2prompt2code/background.js
================================================
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["content_script.js"],
});
});
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "storePageContent") {
const tabId = sender.tab.id;
chrome.storage.local.set({ [tabId]: request.data }, () => {
sendResponse({ success: true });
});
return true;
} else if (request.action === "getPageContent") {
const tabId = sender.tab.id;
chrome.storage.local.get(tabId, (data) => {
sendResponse(data[tabId]);
});
return true;
}
});
================================================
FILE: v0/code2prompt2code/content_script.js
================================================
function storePageContent() {
const pageTitle = document.title;
const pageContent = document.body.innerText;
chrome.runtime.sendMessage({
action: "storePageContent",
data: { title: pageTitle, content: pageContent },
});
}
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "storePageContent") {
storePageContent();
}
});
================================================
FILE: v0/code2prompt2code/manifest.json
================================================
{
"manifest_version": 3,
"name": "code2prompt2code",
"version": "1.0",
"description": "A Chrome extension that summarizes web pages using the Anthropic Claude API.",
"permissions": ["activeTab", "storage"],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"background": {
"service_worker": "background.js",
"type": "module"
},
"content_scripts": [
{
"matches": [""],
"js": ["content_script.js"]
}
],
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
================================================
FILE: v0/code2prompt2code/popup.html
================================================
Anthropic Claude Summary Extension
================================================
FILE: v0/code2prompt2code/popup.js
================================================
document.addEventListener("DOMContentLoaded", () => {
const userPrompt = document.getElementById("userPrompt");
const stylePrompt = document.getElementById("stylePrompt");
const maxTokens = document.getElementById("maxTokens");
const sendButton = document.getElementById("sendButton");
const content = document.getElementById("content");
const loadingIndicator = document.getElementById("loadingIndicator");
sendButton.addEventListener("click", requestAnthropicSummary);
async function requestAnthropicSummary() {
loadingIndicator.style.display = "block";
content.innerHTML = "";
const tab = await new Promise(resolve => chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => resolve(tab)));
chrome.runtime.sendMessage({ action: "getPageContent", tabId: tab.id }, async (pageContent) => {
const prompt = `${userPrompt.value}\n\n${pageContent.title}\n\n${pageContent.content}`;
const style = stylePrompt.value;
const tokens = parseInt(maxTokens.value, 10);
const response = await fetch("https://api.anthropic.com/claude", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ prompt, style, tokens })
});
const summary = await response.json();
content.innerHTML = summary;
loadingIndicator.style.display = "none";
});
}
});
================================================
FILE: v0/code2prompt2code/shared_dependencies.md
================================================
## Shared dependencies:
1. DOM element IDs:
- userPrompt
- stylePrompt
- maxTokens
- sendButton
- content
- loadingIndicator
2. Message names:
- storePageContent
- getPageContent
3. Function names:
- requestAnthropicSummary
- storePageContent
4. Data schemas:
- pageContent: { title: string, content: string }
================================================
FILE: v0/code2prompt2code/styles.css
================================================
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 15px;
}
textarea, input {
display: block;
width: 100%;
margin-bottom: 10px;
padding: 5px;
font-size: 14px;
}
button {
background-color: #4CAF50;
border: none;
color: white;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 14px;
margin: 10px 0;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
#content {
margin-top: 20px;
font-size: 14px;
line-height: 1.5;
}
#loadingIndicator {
display: none;
font-size: 14px;
color: #999;
}
================================================
FILE: v0/constants.py
================================================
EXTENSION_TO_SKIP = [".png",".jpg",".jpeg",".gif",".bmp",".svg",".ico",".tif",".tiff"]
DEFAULT_DIR = "generated"
# we use the 0613 version of the models because we rely on the function calling API
DEFAULT_MODEL = "gpt-4-0613" # we recommend 'gpt-4-0613' if you have it, it will be slower but better at coding. use gpt-4-32k if you have it
# DEFAULT_MODEL = "gpt-3.5-turbo-0613" # gpt3.5 is going to be worse at generating code but faster.
DEFAULT_MAX_TOKENS = 2000 # i wonder how to tweak this properly. we dont want it to be max length as it encourages verbosity of code. but too short and code also truncates suddenly.
================================================
FILE: v0/debugger.py
================================================
import modal
import os
from constants import DEFAULT_DIR, DEFAULT_MODEL, DEFAULT_MAX_TOKENS, EXTENSION_TO_SKIP
stub = modal.Stub("smol-debugger-v1")
openai_image = modal.Image.debian_slim().pip_install("openai")
def read_file(filename):
with open(filename, 'r') as file:
return file.read()
def walk_directory(directory):
code_contents = {}
for dirpath, _, filenames in os.walk(directory):
for filename in filenames:
if not any(filename.endswith(ext) for ext in EXTENSION_TO_SKIP):
try:
relative_filepath = os.path.relpath(os.path.join(dirpath, filename), directory)
code_contents[relative_filepath] = read_file(os.path.join(dirpath, filename))
except Exception as e:
code_contents[relative_filepath] = f"Error reading file {filename}: {str(e)}"
return code_contents
@stub.local_entrypoint()
def main(prompt, directory=DEFAULT_DIR, model="gpt-3.5-turbo"):
code_contents = walk_directory(directory)
# Now, `code_contents` is a dictionary that contains the content of all your non-image files
# You can send this to OpenAI's text-davinci-003 for help
context = "\n".join(f"{path}:\n{contents}" for path, contents in code_contents.items())
system = "You are an AI debugger who is trying to debug a program for a user based on their file system. The user has provided you with the following files and their contents, finally folllowed by the error message or issue they are facing."
prompt = "My files are as follows: " + context + "\n\n" + "My issue is as follows: " + prompt
prompt += "\n\nGive me ideas for what could be wrong and what fixes to do in which files."
res = generate_response.call(system, prompt, model)
# print res in teal
print("\033[96m" + res + "\033[0m")
@stub.function(
image=openai_image,
secret=modal.Secret.from_dotenv(),
retries=modal.Retries(
max_retries=3,
backoff_coefficient=2.0,
initial_delay=1.0,
),
concurrency_limit=5,
timeout=120,
)
def generate_response(system_prompt, user_prompt, model="gpt-3.5-turbo", *args):
import openai
# Set up your OpenAI API credentials
openai.api_key = os.environ["OPENAI_API_KEY"]
messages = []
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": user_prompt})
# loop thru each arg and add it to messages alternating role between "assistant" and "user"
role = "assistant"
for value in args:
messages.append({"role": role, "content": value})
role = "user" if role == "assistant" else "assistant"
params = {
'model': model,
# "model": "gpt-4",
"messages": messages,
"max_tokens": 1500,
"temperature": 0,
}
# Send the API request
response = openai.ChatCompletion.create(**params)
# Get the reply from the API response
reply = response.choices[0]["message"]["content"]
return reply
================================================
FILE: v0/debugger_no_modal.py
================================================
import sys
import os
from time import sleep
from constants import DEFAULT_DIR, DEFAULT_MODEL, DEFAULT_MAX_TOKENS, EXTENSION_TO_SKIP
import argparse
def read_file(filename):
with open(filename, "r") as file:
return file.read()
def walk_directory(directory):
image_extensions = [
".png",
".jpg",
".jpeg",
".gif",
".bmp",
".svg",
".ico",
".tif",
".tiff",
]
code_contents = {}
for root, dirs, files in os.walk(directory):
for file in files:
if not any(file.endswith(ext) for ext in image_extensions):
try:
relative_filepath = os.path.relpath(
os.path.join(root, file), directory
)
code_contents[relative_filepath] = read_file(
os.path.join(root, file)
)
except Exception as e:
code_contents[
relative_filepath
] = f"Error reading file {file}: {str(e)}"
return code_contents
def main(args):
prompt=args.prompt
directory= args.directory
model=args.model
code_contents = walk_directory(directory)
# Now, `code_contents` is a dictionary that contains the content of all your non-image files
# You can send this to OpenAI's text-davinci-003 for help
context = "\n".join(
f"{path}:\n{contents}" for path, contents in code_contents.items()
)
system = "You are an AI debugger who is trying to debug a program for a user based on their file system. The user has provided you with the following files and their contents, finally folllowed by the error message or issue they are facing."
prompt = (
"My files are as follows: "
+ context
+ "\n\n"
+ "My issue is as follows: "
+ prompt
)
prompt += (
"\n\nGive me ideas for what could be wrong and what fixes to do in which files."
)
res = generate_response(system, prompt, model)
# print res in teal
print("\033[96m" + res + "\033[0m")
def generate_response(system_prompt, user_prompt, model=DEFAULT_MODEL, *args):
import openai
# Set up your OpenAI API credentials
openai.api_key = os.environ["OPENAI_API_KEY"]
messages = []
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": user_prompt})
# loop thru each arg and add it to messages alternating role between "assistant" and "user"
role = "assistant"
for value in args:
messages.append({"role": role, "content": value})
role = "user" if role == "assistant" else "assistant"
params = {
"model": model,
# "model": "gpt-4",
"messages": messages,
"max_tokens": 1500,
"temperature": 0,
}
# Send the API request
keep_trying = True
while keep_trying:
try:
response = openai.ChatCompletion.create(**params)
keep_trying = False
except Exception as e:
# e.g. when the API is too busy, we don't want to fail everything
print("Failed to generate response. Error: ", e)
sleep(30)
print("Retrying...")
# Get the reply from the API response
reply = response.choices[0]["message"]["content"]
return reply
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"prompt",
help="The prompt to use for the AI. This should be the error message or issue you are facing.",
)
parser.add_argument(
"--directory",
"-d",
help="The directory to use for the AI. This should be the directory containing the files you want to debug.",
default=DEFAULT_DIR,
)
parser.add_argument(
"--model",
"-m",
help="The model to use for the AI. This should be the model ID of the model you want to use.",
default=DEFAULT_MODEL,
)
args = parser.parse_args()
main(args)
================================================
FILE: v0/exampleChromeExtension/background.js
================================================
chrome.runtime.onInstalled.addListener(() => {
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['content_script.js'],
});
});
});
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'storePageContent') {
chrome.storage.local.set({ pageContent: request }, () => {
sendResponse({ success: true });
});
} else if (request.action === 'getPageContent') {
chrome.storage.local.get(['pageContent'], (result) => {
sendResponse(result);
});
}
return true;
});
================================================
FILE: v0/exampleChromeExtension/content_script.js
================================================
function extractPageContent() {
const pageTitle = document.title;
const pageContent = document.body.innerText;
return { pageTitle, pageContent };
}
function sendMessageToBackground(action, data) {
chrome.runtime.sendMessage({ action, ...data }, (response) => {
if (response.success) {
console.log('Page content sent to background.');
} else {
console.error('Failed to send page content to background.');
}
});
}
const pageData = extractPageContent();
sendMessageToBackground('storePageContent', pageData);
================================================
FILE: v0/exampleChromeExtension/manifest.json
================================================
{
"manifest_version": 3,
"name": "Anthropic Claude Summary Extension",
"version": "1.0",
"description": "A Chrome extension that summarizes web pages using the Anthropic Claude API.",
"permissions": [
"activeTab",
"storage"
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
}
},
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": [""],
"js": ["content_script.js"]
}
],
"icons": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
}
}
================================================
FILE: v0/exampleChromeExtension/popup.html
================================================
Anthropic Summary
================================================
FILE: v0/exampleChromeExtension/popup.js
================================================
document.addEventListener('DOMContentLoaded', () => {
const content = document.getElementById('content');
const userPrompt = document.getElementById('userPrompt');
const sendButton = document.getElementById('sendButton');
const loadingIndicator = document.getElementById('loadingIndicator');
const defaultPrompt = `Human: Please provide a detailed, easy to read HTML summary of the given content with 3-4 highlights per section with important keywords bolded and important links preserved, in this format:
{title here}
{section title here}
{first point}: {short explanation with details, and important links}
{second point}: {short explanation with details, and important links}
{second section here}
With all the words in brackets replaced by the summary of the content.
Assistant:`;
function requestAnthropicSummary(prompt, apiKey) {
chrome.runtime.sendMessage({ action: 'getPageContent' }, (response) => {
const { pageTitle, pageContent } = response.pageContent;
const fullPrompt = `${pageTitle}\n\n${pageContent}\n\n${prompt}`;
loadingIndicator.innerHTML = `Read page content of ${pageTitle}`;
loadingIndicator.style.display = 'block';
content.innerHTML = '';
userPrompt.disabled = true;
sendButton.disabled = true;
fetch('https://api.anthropic.com/v1/complete', {
method: 'POST',
headers: {
'x-api-key': apiKey,
'content-type': 'application/json',
},
body: JSON.stringify({
prompt: fullPrompt,
model: 'claude-instant-v1-100k',
max_tokens_to_sample: 1000,
stop_sequences: ['\n\nHuman:'],
}),
})
.then((res) => {
if (res.status === 401) {
chrome.storage.local.remove(['apiKey']);
throw new Error('Invalid API key');
}
return res.json();
})
.then((data) => {
content.innerHTML = data.completion;
loadingIndicator.style.display = 'none';
userPrompt.disabled = false;
sendButton.disabled = false;
})
.catch((error) => {
console.error(error);
loadingIndicator.style.display = 'none';
userPrompt.disabled = false;
sendButton.disabled = false;
});
});
}
chrome.storage.local.get(['apiKey'], (result) => {
if (!result.apiKey) {
const apiKey = prompt('Please enter your Anthropic Claude API key:');
chrome.storage.local.set({ apiKey }, () => {
requestAnthropicSummary(defaultPrompt, apiKey);
});
} else {
requestAnthropicSummary(defaultPrompt, result.apiKey);
}
});
sendButton.addEventListener('click', () => {
chrome.storage.local.get(['apiKey'], (result) => {
requestAnthropicSummary(userPrompt.value, result.apiKey);
});
});
});
================================================
FILE: v0/exampleChromeExtension/prompt used for this extension.md
================================================
a Chrome Manifest V3 extension that reads the current page, and offers a popup UI that sends the page content to the Anthropic Claude API along with a prompt to summarize it, and lets the user modify that prompt and re-send the prompt+content to get another summary view of the content.
- When clicked:
- it injects a content script `content_script.js` on the currently open tab,
and accesses the title `pageTitle` and main content `pageContent` of the currently open page
(extracted via an injected content script, and sent over using a `storePageContent` action)
- in the background, receives the `storePageContent` data and stores it
- pops up a small window with a simple, modern, slick, minimalistic styled html popup
- in the popup script
- retrieves the page content data using a `getPageContent` action (and the background listens for the `getPageContent` action and retrieves that data)
- check extension storage for an `apiKey`, and if it isn't stored, asks for an API key to Anthropic Claude and stores it.
- calls the Anthropic model endpoint https://api.anthropic.com/v1/complete with the `claude-instant-v1-100k` model with:
- append the page title
- append the page content
- append a prompt to ask for a detailed, easy to read HTML summary of the given content with 3-4 highlights per section with important keywords bolded and important links preserved.
in this format:
```js
defaultPrompt = `Human: Please provide a detailed, easy to read HTML summary of the given content
with 3-4 highlights per section with important keywords bolded and important links preserved, in this format:
{title here}
{section title here}
{first point}: {short explanation with details, with any relevant links included}
{second point}: {short explanation with details, with any relevant links included}
{third point}:
{second section here}
With all the words in brackets replaced by the summary of the content. Only draw from the source content, do not hallucinate.
Assistant:`;
```js
- renders the Anthropic-generated HTML summary inside of the popup in a div with an id of content
- at the bottom of the popup, show a textarea with an id of `userPrompt` with a short default value prompt, and a submit button with an id of `sendButton`.
- when `sendButton` is clicked, lets the user re-ask Anthropic with the same page title and page content but different prompt (from `userPrompt`).
- disable these inputs while it waits for the Anthropic api call to complete
Important Details:
- It has to run in a browser environment, so no Nodejs APIs allowed.
- the popup should show a "Read page content of {TITLE}" message with a big fat attractive juicy css animated candy stripe loading indicator `loadingIndicator` while waiting for the anthropic api to return, with the TITLE being the page title. do not show it until the api call begins, and clear it when it ends.
- the return signature of the anthropic api is curl https://api.anthropic.com/v1/complete\
-H "x-api-key: $API_KEY"\
-H 'content-type: application/json'\
-d '{
"prompt": "\n\nHuman: Tell me a haiku about trees\n\nAssistant: ",
"model": "claude-v1", "max_tokens_to_sample": 1000, "stop_sequences": ["\n\nHuman:"]
}'
{"completion":" Here is a haiku about trees:\n\nSilent sentinels, \nStanding solemn in the woods,\nBranches reaching sky.","stop":"\n\nHuman:","stop_reason":"stop_sequence","truncated":false,"log_id":"f5d95cf326a4ac39ee36a35f434a59d5","model":"claude-v1","exception":null}
- in the string prompt sent to Anthropic, first include the page title and page content, and finally append the prompt, clearly vertically separated by spacing.
- if the Anthropic api call is a 401, handle that by clearing the stored anthropic api key and asking for it again.
- add styles to make sure the popup's styling follows the basic rules of web design, for example having margins around the body, and a system font stack.
- style the popup body with a minimum width of 400 and height of 600.
## debugging notes
inside of background.js, just take the getPageContent response directly
```js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'storePageContent') {
// dont access request.pageContent
chrome.storage.local.set({ pageContent: request }, () => {
sendResponse({ success: true });
});
} else if (request.action === 'getPageContent') {
chrome.storage.local.get(['pageContent'], (result) => {
// dont access request.pageContent
sendResponse(result);
});
}
return true;
});
```
inside of popup.js, Update the function calls to `requestAnthropicSummary`
in `popup.js` to pass the `apiKey`:
```javascript
chrome.storage.local.get(['apiKey'], (result) => {
const apiKey = result.apiKey;
requestAnthropicSummary(defaultPrompt, apiKey);
});
sendButton.addEventListener('click', () => {
chrome.storage.local.get(['apiKey'], (result) => {
const apiKey = result.apiKey;
requestAnthropicSummary(userPrompt.value, apiKey);
});
});
```
in `popup.js`, store the defaultPrompt at the top level.
also, give a HTML format to the anthropic prompt
================================================
FILE: v0/exampleChromeExtension/shared_dependencies.md
================================================
the app is: a Chrome Manifest V3 extension that reads the current page, and offers a popup UI that sends the page content to the Anthropic Claude API along with a prompt to summarize it, and lets the user modify that prompt and re-send the prompt+content to get another summary view of the content.
the files we have decided to generate are: content_script.js, background.js, popup.html, popup.js, popup.css
Shared dependencies:
1. Exported variables:
- pageTitle
- pageContent
2. Data schemas:
- storePageContent action data: { action: 'storePageContent', pageTitle, pageContent }
- getPageContent action data: { action: 'getPageContent' }
3. ID names of DOM elements:
- content
- userPrompt
- sendButton
- loadingIndicator
4. Message names:
- storePageContent
- getPageContent
5. Function names:
- requestAnthropicSummary
6. API endpoints:
- https://api.anthropic.com/v1/complete
7. Model name:
- claude-instant-v1-100k
8. Default prompt:
- defaultPrompt
================================================
FILE: v0/exampleChromeExtension/styles.css
================================================
body {
margin: 16px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
min-width: 400px;
min-height: 600px;
}
#content {
margin-bottom: 16px;
}
#userPrompt {
width: 100%;
height: 80px;
resize: none;
margin-bottom: 8px;
}
#sendButton {
display: inline-block;
background-color: #007bff;
color: white;
padding: 8px 16px;
border-radius: 4px;
text-decoration: none;
cursor: pointer;
transition: background-color 0.2s;
}
#sendButton:hover {
background-color: #0056b3;
}
#sendButton:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
#loadingIndicator {
display: none;
width: 100%;
height: 8rem;
border-radius: 1rem;
margin-bottom: 2rem;
background-image: linear-gradient(-45deg, #007bff 25%, transparent 25%, transparent 50%, #007bff 50%, #007bff 75%, transparent 75%, transparent);
background-size: 40px 40px;
animation: loading 1s linear infinite;
}
@keyframes loading {
0% {
background-position: 40px 0;
}
100% {
background-position: 0 0;
}
}
================================================
FILE: v0/main.py
================================================
import os
import modal
import ast
from utils import clean_dir
from constants import DEFAULT_DIR, DEFAULT_MODEL, DEFAULT_MAX_TOKENS
stub = modal.Stub("smol-developer-v1") # yes we are recommending using Modal by default, as it helps with deployment. see readme for why.
openai_image = modal.Image.debian_slim().pip_install("openai", "tiktoken")
@stub.function(
image=openai_image,
secret=modal.Secret.from_dotenv(),
retries=modal.Retries(
max_retries=5,
backoff_coefficient=2.0,
initial_delay=1.0,
),
concurrency_limit=5, # many users report rate limit issues (https://github.com/smol-ai/developer/issues/10) so we try to do this but it is still inexact. would like ideas on how to improve
timeout=120,
)
def generate_response(model, system_prompt, user_prompt, *args):
# IMPORTANT: Keep import statements here due to Modal container restrictions https://modal.com/docs/guide/custom-container#additional-python-packages
import openai
import tiktoken
def reportTokens(prompt):
encoding = tiktoken.encoding_for_model(model)
# print number of tokens in light gray, with first 50 characters of prompt in green. if truncated, show that it is truncated
print("\033[37m" + str(len(encoding.encode(prompt))) + " tokens\033[0m" + " in prompt: " + "\033[92m" + prompt[:50] + "\033[0m" + ("..." if len(prompt) > 50 else ""))
# Set up your OpenAI API credentials
openai.api_key = os.environ["OPENAI_API_KEY"]
messages = []
messages.append({"role": "system", "content": system_prompt})
reportTokens(system_prompt)
messages.append({"role": "user", "content": user_prompt})
reportTokens(user_prompt)
# Loop through each value in `args` and add it to messages alternating role between "assistant" and "user"
role = "assistant"
for value in args:
messages.append({"role": role, "content": value})
reportTokens(value)
role = "user" if role == "assistant" else "assistant"
params = {
"model": model,
"messages": messages,
"max_tokens": DEFAULT_MAX_TOKENS,
"temperature": 0,
}
# Send the API request
response = openai.ChatCompletion.create(**params)
# Get the reply from the API response
reply = response.choices[0]["message"]["content"]
return reply
@stub.function()
def generate_file(filename, model=DEFAULT_MODEL, filepaths_string=None, shared_dependencies=None, prompt=None):
# call openai api with this prompt
filecode = generate_response.call(model,
f"""You are an AI developer who is trying to write a program that will generate code for the user based on their intent.
the app is: {prompt}
the files we have decided to generate are: {filepaths_string}
the shared dependencies (like filenames and variable names) we have decided on are: {shared_dependencies}
only write valid code for the given filepath and file type, and return only the code.
do not add any other explanation, only return valid code for that file type.
""",
f"""
We have broken up the program into per-file generation.
Now your job is to generate only the code for the file {filename}.
Make sure to have consistent filenames if you reference other files we are also generating.
Remember that you must obey 3 things:
- you are generating code for the file {filename}
- do not stray from the names of the files and the shared dependencies we have decided on
- MOST IMPORTANT OF ALL - the purpose of our app is {prompt} - every line of code you generate must be valid code. Do not include code fences in your response, for example
Bad response:
```javascript
console.log("hello world")
```
Good response:
console.log("hello world")
Begin generating the code now.
""",
)
return filename, filecode
@stub.local_entrypoint()
def main(prompt, directory=DEFAULT_DIR, model=DEFAULT_MODEL, file=None):
# read file from prompt if it ends in a .md filetype
if prompt.endswith(".md"):
with open(prompt, "r") as promptfile:
prompt = promptfile.read()
print("hi its me, 🐣the smol developer🐣! you said you wanted:")
# print the prompt in green color
print("\033[92m" + prompt + "\033[0m")
# call openai api with this prompt
filepaths_string = generate_response.call(model,
"""You are an AI developer who is trying to write a program that will generate code for the user based on their intent.
When given their intent, create a complete, exhaustive list of filepaths that the user would write to make the program.
only list the filepaths you would write, and return them as a python list of strings.
do not add any other explanation, only return a python list of strings.
Example output:
["index.html", "style.css", "script.js"]
""",
prompt,
)
print(filepaths_string)
# parse the result into a python list
list_actual = []
try:
list_actual = ast.literal_eval(filepaths_string)
# if shared_dependencies.md is there, read it in, else set it to None
shared_dependencies = None
if os.path.exists("shared_dependencies.md"):
with open("shared_dependencies.md", "r") as shared_dependencies_file:
shared_dependencies = shared_dependencies_file.read()
if file is not None:
# check file
print("file", file)
filename, filecode = generate_file(file, model=model, filepaths_string=filepaths_string, shared_dependencies=shared_dependencies, prompt=prompt)
write_file(filename, filecode, directory)
else:
clean_dir(directory)
# understand shared dependencies
shared_dependencies = generate_response.call(model,
"""You are an AI developer who is trying to write a program that will generate code for the user based on their intent.
In response to the user's prompt:
---
the app is: {prompt}
---
the files we have decided to generate are: {filepaths_string}
Now that we have a list of files, we need to understand what dependencies they share.
Please name and briefly describe what is shared between the files we are generating, including exported variables, data schemas, id names of every DOM elements that javascript functions will use, message names, and function names.
Exclusively focus on the names of the shared dependencies, and do not add any other explanation.
""",
prompt,
)
print(shared_dependencies)
# write shared dependencies as a md file inside the generated directory
write_file("shared_dependencies.md", shared_dependencies, directory)
# Iterate over generated files and write them to the specified directory
for filename, filecode in generate_file.map(
list_actual, order_outputs=False, kwargs=dict(model=model, filepaths_string=filepaths_string, shared_dependencies=shared_dependencies, prompt=prompt)
):
write_file(filename, filecode, directory)
except ValueError:
print("Failed to parse result")
def write_file(filename, filecode, directory):
# Output the filename in blue color
print("\033[94m" + filename + "\033[0m")
print(filecode)
file_path = os.path.join(directory, filename)
dir = os.path.dirname(file_path)
# Check if the filename is actually a directory
if os.path.isdir(file_path):
print(f"Error: {filename} is a directory, not a file.")
return
os.makedirs(dir, exist_ok=True)
# Open the file in write mode
with open(file_path, "w") as file:
# Write content to the file
file.write(filecode)
================================================
FILE: v0/main_no_modal.py
================================================
import sys
import os
import ast
from time import sleep
from utils import clean_dir
from constants import DEFAULT_DIR, DEFAULT_MODEL, DEFAULT_MAX_TOKENS
def generate_response(system_prompt, user_prompt, *args):
import openai
import tiktoken
def reportTokens(prompt):
encoding = tiktoken.encoding_for_model(DEFAULT_MODEL)
# print number of tokens in light gray, with first 10 characters of prompt in green
print(
"\033[37m"
+ str(len(encoding.encode(prompt)))
+ " tokens\033[0m"
+ " in prompt: "
+ "\033[92m"
+ prompt[:50]
+ "\033[0m"
)
# Set up your OpenAI API credentials
openai.api_key = os.environ["OPENAI_API_KEY"]
messages = []
messages.append({"role": "system", "content": system_prompt})
reportTokens(system_prompt)
messages.append({"role": "user", "content": user_prompt})
reportTokens(user_prompt)
# loop thru each arg and add it to messages alternating role between "assistant" and "user"
role = "assistant"
for value in args:
messages.append({"role": role, "content": value})
reportTokens(value)
role = "user" if role == "assistant" else "assistant"
params = {
"model": DEFAULT_MODEL,
"messages": messages,
"max_tokens": DEFAULT_MAX_TOKENS,
"temperature": 0,
}
# Send the API request
keep_trying = True
while keep_trying:
try:
response = openai.ChatCompletion.create(**params)
keep_trying = False
except Exception as e:
# e.g. when the API is too busy, we don't want to fail everything
print("Failed to generate response. Error: ", e)
sleep(30)
print("Retrying...")
# Get the reply from the API response
reply = response.choices[0]["message"]["content"]
return reply
def generate_file(
filename, filepaths_string=None, shared_dependencies=None, prompt=None
):
# call openai api with this prompt
filecode = generate_response(
f"""You are an AI developer who is trying to write a program that will generate code for the user based on their intent.
the app is: {prompt}
the files we have decided to generate are: {filepaths_string}
the shared dependencies (like filenames and variable names) we have decided on are: {shared_dependencies}
only write valid code for the given filepath and file type, and return only the code.
do not add any other explanation, only return valid code for that file type.
""",
f"""
We have broken up the program into per-file generation.
Now your job is to generate only the code for the file {filename}.
Make sure to have consistent filenames if you reference other files we are also generating.
Remember that you must obey 3 things:
- you are generating code for the file {filename}
- do not stray from the names of the files and the shared dependencies we have decided on
- MOST IMPORTANT OF ALL - the purpose of our app is {prompt} - every line of code you generate must be valid code. Do not include code fences in your response, for example
Bad response:
```javascript
console.log("hello world")
```
Good response:
console.log("hello world")
Begin generating the code now.
""",
)
return filename, filecode
def main(prompt, directory=DEFAULT_DIR, file=None):
# read file from prompt if it ends in a .md filetype
if prompt.endswith(".md"):
with open(prompt, "r") as promptfile:
prompt = promptfile.read()
print("hi its me, 🐣the smol developer🐣! you said you wanted:")
# print the prompt in green color
print("\033[92m" + prompt + "\033[0m")
# example prompt:
# a Chrome extension that, when clicked, opens a small window with a page where you can enter
# a prompt for reading the currently open page and generating some response from openai
# call openai api with this prompt
filepaths_string = generate_response(
"""You are an AI developer who is trying to write a program that will generate code for the user based on their intent.
When given their intent, create a complete, exhaustive list of filepaths that the user would write to make the program.
only list the filepaths you would write, and return them as a python list of strings.
do not add any other explanation, only return a python list of strings.
""",
prompt,
)
print(filepaths_string)
# parse the result into a python list
list_actual = []
try:
list_actual = ast.literal_eval(filepaths_string)
# if shared_dependencies.md is there, read it in, else set it to None
shared_dependencies = None
if os.path.exists("shared_dependencies.md"):
with open("shared_dependencies.md", "r") as shared_dependencies_file:
shared_dependencies = shared_dependencies_file.read()
if file is not None:
# check file
print("file", file)
filename, filecode = generate_file(
file,
filepaths_string=filepaths_string,
shared_dependencies=shared_dependencies,
prompt=prompt,
)
write_file(filename, filecode, directory)
else:
clean_dir(directory)
# understand shared dependencies
shared_dependencies = generate_response(
"""You are an AI developer who is trying to write a program that will generate code for the user based on their intent.
In response to the user's prompt:
---
the app is: {prompt}
---
the files we have decided to generate are: {filepaths_string}
Now that we have a list of files, we need to understand what dependencies they share.
Please name and briefly describe what is shared between the files we are generating, including exported variables, data schemas, id names of every DOM elements that javascript functions will use, message names, and function names.
Exclusively focus on the names of the shared dependencies, and do not add any other explanation.
""",
prompt,
)
print(shared_dependencies)
# write shared dependencies as a md file inside the generated directory
write_file("shared_dependencies.md", shared_dependencies, directory)
for name in list_actual:
filename, filecode = generate_file(
name,
filepaths_string=filepaths_string,
shared_dependencies=shared_dependencies,
prompt=prompt,
)
write_file(filename, filecode, directory)
except ValueError:
print("Failed to parse result: " + result)
def write_file(filename, filecode, directory):
# Output the filename in blue color
print("\033[94m" + filename + "\033[0m")
print(filecode)
file_path = directory + "/" + filename
dir = os.path.dirname(file_path)
os.makedirs(dir, exist_ok=True)
# if file_path does not end with "/"
if not file_path.endswith("/"):
with open(file_path, "w") as file:
# Write content to the file
file.write(filecode)
if __name__ == "__main__":
# Check for arguments
if len(sys.argv) < 2:
# Looks like we don't have a prompt. Check if prompt.md exists
if not os.path.exists("prompt.md"):
# Still no? Then we can't continue
print("Please provide a prompt")
sys.exit(1)
# Still here? Assign the prompt file name to prompt
prompt = "prompt.md"
else:
# Set prompt to the first argument
prompt = sys.argv[1]
# Pull everything else as normal
directory = sys.argv[2] if len(sys.argv) > 2 else DEFAULT_DIR
file = sys.argv[3] if len(sys.argv) > 3 else None
# Run the main function
main(prompt, directory, file)
================================================
FILE: v0/readme.md
================================================
this is the archive for v0 of smol developer, which was the original Modal.com based version that we found (well, knew) had a few issues with reliability and dependency management.
it has been rewritten from scratch in the root folder, but is preserved here for others to easily reference.
================================================
FILE: v0/static/readme.md
================================================
by default, files in here will be copied into the generated folder, useful for adding non-code files like images and large files like csv's.
================================================
FILE: v0/utils.py
================================================
import os
from constants import EXTENSION_TO_SKIP
def clean_dir(directory):
# Check if the directory exists
if os.path.exists(directory):
# If it does, iterate over all files and directories
for dirpath, _, filenames in os.walk(directory):
for filename in filenames:
_, extension = os.path.splitext(filename)
if extension not in EXTENSION_TO_SKIP:
os.remove(os.path.join(dirpath, filename))
else:
os.makedirs(directory, exist_ok=True)
================================================
FILE: v0/v0_readme.md
================================================
# smol developer
***Human-centric & Coherent Whole Program Synthesis*** aka your own personal junior developer
> [Build the thing that builds the thing!](https://twitter.com/swyx/status/1657578738345979905) a `smol dev` for every dev in every situation
this is a prototype of a "junior developer" agent (aka `smol dev`) that scaffolds an entire codebase out for you once you give it a product spec, but does not end the world or overpromise AGI. instead of making and maintaining specific, rigid, one-shot starters, like `create-react-app`, or `create-nextjs-app`, this is basically [`create-anything-app`](https://news.ycombinator.com/item?id=35942352) where you develop your scaffolding prompt in a tight loop with your smol dev.
AI that is helpful, harmless, and honest is complemented by a codebase that is simple, safe, and smol - <200 lines of Python and Prompts, so this is easy to understand and customize.
*Not no code, not low code, but some third thing.*
Perhaps a higher order evolution of programming where you still need to be technical, but no longer have to implement every detail at least to scaffold things out.
## examples/prompt gallery
- [6 minute video demo](https://youtu.be/UCo7YeTy-aE) - (sorry for sped up audio, we were optimizing for twitter, bad call)
- this was the original smol developer demo - going from prompt to full chrome extension that requests and stores and apikey, generates a popup window, reads and transmits page content, and usefully summarizes any website with Anthropic Claude, switching models up to the 100k one based on length of input
- the prompt is located in [prompt.md](https://github.com/smol-ai/developer/blob/main/prompt.md) and it outputs [/exampleChromeExtension](https://github.com/smol-ai/developer/tree/main/examples/exampleChromeExtension)
- `smol-plugin` - prompt to ChatGPT plugin ([tweet](https://twitter.com/ultrasoundchad/status/1659366507409985536?s=20), [fork](https://github.com/gmchad/smol-plugin))
- [Prompt to Pokemon App](https://twitter.com/RobertCaracaus/status/1659312419485761536?s=20)
- [Political Campaign CRM Program example](https://github.com/smol-ai/developer/pull/22/files)
- [Lessons from Creating a VSCode Extension with GPT-4](https://bit.kevinslin.com/p/leveraging-gpt-4-to-automate-the) (also on [HN](https://news.ycombinator.com/item?id=36071342))
- [7 min Video: Smol AI Developer - Build ENTIRE Codebases With A Single Prompt](https://www.youtube.com/watch?v=DzRoYc2UGKI) produces a full working OpenAI CLI python app from a prompt
- [12 min Video: SMOL AI - Develop Large Scale Apps with AGI in one click](https://www.youtube.com/watch?v=zsxyqz6SYp8) scaffolds a surprisingly complex React/Node/MongoDB full stack app in 40 minutes and $9
I'm actively seeking more examples, please PR yours!
sorry for the lack of examples, I know that is frustrating but I wasnt ready for so many of you lol
## major forks/alternatives
please send in alternative implementations, and deploy strategies on alternative stacks!
- **JS/TS**: https://github.com/PicoCreator/smol-dev-js A pure JS variant of smol-dev, allowing even smoler incremental changes via prompting (if you dun want to do the whole spec2code thing), allowing you to plug it into any project live (for better or worse)
- **C#/Dotnet**: https://github.com/colhountech/smol-ai-dotnet in C#!
- **Golang**: https://github.com/tmc/smol-dev-go in Go
- https://github.com/gmchad/smol-plugin automatically generate @openai plugins by specifying your API in markdown in smol-developer style
- your fork here!
## arch diagram
naturally generated with gpt4, like [we did for babyagi](https://twitter.com/swyx/status/1648724820316786688)

### innovations and insights
> Please subscribe to https://latent.space/ for a fuller writeup and insights and reflections
- **Markdown is all you need** - Markdown is the perfect way to prompt for whole program synthesis because it is easy to mix english and code (whether `variable_names` or entire \`\`\` code fenced code samples)
- turns out you can specify prompts in code in prompts and gpt4 obeys that to the letter
- **Copy and paste programming**
- teaching the program to understand how to code around a new API (Anthropic's API is after GPT3's knowledge cutoff) by just pasting in the `curl` input and output
- pasting error messages into the prompt and vaguely telling the program how you'd like it handled. it kind of feels like "logbook driven programming".
- **Debugging by `cat`ing** the whole codebase with your error message and getting specific fix suggestions - particularly delightful!
- **Tricks for whole program coherence** - our chosen example usecase, Chrome extensions, have a lot of indirect dependencies across files. Any hallucination of cross dependencies causes the whole program to error.
- We solved this by adding an intermediate step asking GPT to think through `shared_dependencies.md`, and then insisting on using that in generating each file. This basically means GPT is able to talk to itself...
- ... but it's not perfect, yet. `shared_dependencies.md` is sometimes not comperehensive in understanding what are hard dependencies between files. So we just solved it by specifying a specific `name` in the prompt. felt dirty at first but it works, and really it's just clear unambiguous communication at the end of the day.
- see `prompt.md` for SOTA smol-dev prompting
- **Low activation energy for unfamiliar APIs**
- we have never really learned css animations, but now can just say we want a "juicy css animated red and white candy stripe loading indicator" and it does the thing.
- ditto for Chrome Extension Manifest v3 - the docs are an abject mess, but fortunately we don't have to read them now to just get a basic thing done
- the Anthropic docs (bad bad) were missing guidance on what return signature they have. so just curl it and dump it in the prompt lol.
- **Modal is all you need** - we chose Modal to solve 4 things:
- solve python dependency hell in dev and prod
- parallelizable code generation
- simple upgrade path from local dev to cloud hosted endpoints (in future)
- fault tolerant openai api calls with retries/backoff, and attached storage (for future use)
> Please subscribe to https://latent.space/ for a fuller writeup and insights and reflections
### caveats
We were working on a Chrome Extension, which requires images to be generated, so we added some usecase specific code in there to skip destroying/regenerating them, that we haven't decided how to generalize.
We dont have access to GPT4-32k, but if we did, we'd explore dumping entire API/SDK documentation into context.
The feedback loop is very slow right now (`time` says about 2-4 mins to generate a program with GPT4, even with parallelization due to Modal (occasionally spiking higher)), but it's a safe bet that it will go down over time (see also "future directions" below).
## install
it's basically:
- `git clone https://github.com/smol-ai/developer`.
- copy over `.example.env` to `.env` filling in your API keys.
There are no python dependencies to wrangle thanks to using Modal as a [self-provisioning runtime](https://www.google.com/search?q=self+provisioning+runtime).
Unfortunately this project also uses 3 other things:
- Modal.com - [sign up](https://modal.com/signup), then `pip install modal-client && modal token new`
- You can run this project w/o Modal following these instructions:
- `pip install -r requirements.txt`
- `export OPENAI_API_KEY=sk-xxxxxx` (your openai api key here)
- `python main_no_modal.py YOUR_PROMPT_HERE`
- GPT-4 api (private beta) - this project now defaults to using `gpt-3.5-turbo` but it obviously wont be as good. we are working on a hosted version so you can try this out on our keys.
- (for the demo project only) anthropic claude 100k context api (private beta) - not important unless you're exactly trying to repro my demo
you'll have to adapt this code on a fork if you want to use it on other infra. please open issues/PRs and i'll happily highlight your fork here.
### trying the example chrome extension from the demo video
the `/examples/exampleChromeExtension` folder contains `a Chrome Manifest V3 extension that reads the current page, and offers a popup UI that has the page title+content and a textarea for a prompt (with a default value we specify). When the user hits submit, it sends the page title+content to the Anthropic Claude API along with the up to date prompt to summarize it. The user can modify that prompt and re-send the prompt+content to get another summary view of the content.`
- go to Manage Extensions in Chrome
- load unpacked
- find the relevant folder in your file system and load it
- go to any content heavy site
- click the cute bird
- see it work and rejoice
this entire extension was generated by the prompt in `prompt.md` (except for the images), and was built up over time by adding more words to the prompt in an iterative process.
## usage: smol dev
basic usage (by default it runs with `gpt-3.5-turbo`, but we strongly encourage running with `gpt-4` if you have access)
```bash
# inline prompt
modal run main.py --prompt "a Chrome extension that, when clicked, opens a small window with a page where you can enter a prompt for reading the currently open page and generating some response from openai" --model=gpt-4
```
after a while of adding to your prompt, you can extract your prompt to a file, as long as your "prompt" ends in a .md extension we'll go look for that file
```bash
# prompt in markdown file
modal run main.py --prompt prompt.md --model=gpt-4
```
each time you run this, the generated directory is deleted (except for images) and all files are rewritten from scratch.
In the `shared_dependencies.md` file is a helper file that ensures coherence between files. This is in the process of being expanded into an official `--plan` functionality (see https://github.com/smol-ai/developer/issues/12)
### smol dev in single file mode
if you make a tweak to the prompt and only want it to affect one file, and keep the rest of the files, specify the file param:
```bash
modal run main.py --prompt prompt.md --file popup.js
```
### smol dev without modal.com
By default, `main.py` uses Modal, beacuse it provides a nice upgrade path to a hosted experience (coming soon, so you can try it out without needing GPT4 key access).
However if you want to just run it on your own machine, you can run smol dev w/o Modal following these instructions:
```bash
pip install -r requirements.txt
export OPENAI_API_KEY=sk-xxxxxx # your openai api key here)
python main_no_modal.py YOUR_PROMPT_HERE
```
If no command line argument is given, **and** the file `prompt.md` exists, the main function will automatically use the `prompt.md` file. All other command line arguments are left as default. *this is handy for those using the "run" function on a `venv` setup in PyCharm for Windows, where no opportunity is given to enter command line arguments. Thanks [@danmenzies](https://github.com/smol-ai/developer/pull/55)*
## usage: smol debugger
*this is a beta feature, very very MVP, just a proof of concept really*
take the entire contents of the generated directory in context, feed in an error, get a response. this basically takes advantage of longer (32k-100k) context so we basically dont have to do any embedding of the source.
```bash
modal run debugger.py --prompt "Uncaught (in promise) TypeError: Cannot destructure property 'pageTitle' of '(intermediate value)' as it is undefined. at init (popup.js:59:11)"
# gpt4
modal run debugger.py --prompt "your_error msg_here" --model=gpt-4
```
## usage: smol pm
*this is even worse than beta, its kind of a "let's see what happens" experiment*
take the entire contents of the generated directory in context, and get a prompt back that could synthesize the whole program. basically `smol dev`, in reverse.
```bash
modal run code2prompt.py # ~0.5 second with gpt 3.5
# use gpt4
modal run code2prompt.py --model=gpt-4 # 2 mins, MUCH better results
```
We have done indicative runs of both, stored in `examples/code2prompt/code2prompt-gpt3.md` vs `examples/code2prompt/code2prompt-gpt4.md`. Note how incredibly better gpt4 is at prompt engineering its future self.
Naturally, we had to try `code2prompt2code`...
```bash
# add prompt... this needed a few iterations to get right
modal run code2prompt.py --prompt "make sure all the id's of the DOM elements, and the data structure of the page content (stored with {pageTitle, pageContent }) , referenced/shared by the js files match up exactly. take note to only use Chrome Manifest V3 apis. rename the extension to code2prompt2code" --model=gpt-4 # takes 4 mins. produces semi working chrome extension copy based purely on the model-generated description of a different codebase
# must go deeper
modal run main.py --prompt code2prompt-gpt4.md --directory code2prompt2code
```
We leave the social and technical impacts of multilayer generative deep-frying of codebases as an exercise to the reader.
## Development using a Dev Container
> this is a [new addition](https://github.com/smol-ai/developer/pull/30)! Please try it out and send in fixes if there are any issues.
We have configured a development container for this project, which provides an isolated and consistent development environment. This approach is ideal for developers using Visual Studio Code's Remote - Containers extension or GitHub's Codespaces.
If you have [VS Code](https://code.visualstudio.com/download) and [Docker](https://www.swyx.io/running-docker-without-docker-desktop) installed on your machine, you can make use of the devcontainer to create an isolated environment with all dependencies automatically installed and configured. This is a great way to ensure a consistent development experience across different machines.
Here are the steps to use the devcontainer:
1. Open this project in VS Code.
2. When prompted to "Reopen in Container", choose "Reopen in Container". This will start the process of building the devcontainer defined by the `Dockerfile` and `.devcontainer.json` in the `.devcontainer` directory.
3. Wait for the build to finish. The first time will be a bit longer as it downloads and builds everything. Future loads will be much faster.
4. Once the build is finished, the VS Code window will reload and you are now working inside the devcontainer.
Benefits of a Dev Container
1. **Consistent Environment**: Every developer works within the same development setup, eliminating "it works on my machine" issues and easing the onboarding of new contributors.
2. **Sandboxing**: Your development environment is isolated from your local machine, allowing you to work on multiple projects with differing dependencies without conflict.
3. **Version Control for Environments**: Just as you version control your source code, you can do the same with your development environment. If a dependency update introduces issues, it's easy to revert to a previous state.
4. **Easier CI/CD Integration**: If your CI/CD pipeline utilizes Docker, your testing environment will be identical to your local development environment, ensuring consistency across development, testing, and production setups.
5. **Portability**: This setup can be utilized on any computer with Docker and the appropriate IDE installed. Simply clone the repository and start the container.
## future directions
things to try/would accept open issue discussions and PRs:
- **specify .md files for each generated file**, with further prompts that could finetune the output in each of them
- so basically like `popup.html.md` and `content_script.js.md` and so on
- **bootstrap the `prompt.md`** for existing codebases - write a script to read in a codebase and write a descriptive, bullet pointed prompt that generates it
- done by `smol pm`, but its not very good yet - would love for some focused polish/effort until we have quine smol developer that can generate itself lmao
- **ability to install its own dependencies**
- this leaks into depending on the execution environment, which we all know is the path to dependency madness. how to avoid? dockerize? nix? [web container](https://twitter.com/litbid/status/1658154530385670150)?
- Modal has an interesting possibility: generate functions that speak modal which also solves the dependency thing https://twitter.com/akshat_b/status/1658146096902811657
- **self-heal** by running the code itself and use errors as information for reprompting
- however its a bit hard to get errors from the chrome extension environment so we did not try this
- **using anthropic as the coding layer**
- you can run `modal run anthropic.py --prompt prompt.md --outputdir=anthropic` to try it
- but it doesnt work because anthropic doesnt follow instructions to generate file code very well.
- **make agents that autonomously run this code in a loop/watch the prompt file** and regenerate code each time, on a new git branch
- the code could be generated on 5 simultaneous git branches and checking their output would just involve switching git branches