[
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n    \"name\": \"Python Project\", // The name of your dev container setup, can be anything you choose.\n    \"dockerFile\": \"Dockerfile\", // The path to the Dockerfile that describes your development environment.\n    \"settings\": { \n        \"terminal.integrated.shell.linux\": \"/bin/bash\", // Specifies the shell to be used in the integrated terminal in VS Code.\n        \"python.pythonPath\": \"/usr/local/bin/python\", // Specifies the path to the Python interpreter.\n        \"python.linting.pylintEnabled\": true, // Enables linting using pylint for Python files.\n        \"python.linting.enabled\": true // Enables linting for Python files in general.\n    },\n    \"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.\n    \"forwardPorts\": [], // Specifies any ports that should be forwarded from the dev container to the host.\n    \"postCreateCommand\": \"pip install -r requirements.txt\" // Specifies a command or series of commands to run after the dev container is created.\n}\n"
  },
  {
    "path": ".devcontainer/dockerfile",
    "content": "# syntax=docker/dockerfile:1   # Specifies Dockerfile version to use. In this case, version 1.\n\nFROM 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.\n\nWORKDIR /app   # Sets the working directory in the Docker container. Any command that follows in the Dockerfile will be run in this directory.\n\nCOPY requirements.txt .  # Copies the requirements.txt file from your project to the working directory in the Docker image.\n\nRUN pip install -r requirements.txt  # Runs pip install command in your Docker image to install Python dependencies listed in your requirements.txt file.\n\nCOPY . .  # Copies everything else in your project (denoted by '.') to the working directory in the Docker image.\n"
  },
  {
    "path": ".example.env",
    "content": "OPENAI_API_KEY=sk-xxxxxx"
  },
  {
    "path": ".gitignore",
    "content": ".env\nenv\n__pycache__\n.vscode\n.idea\n/.idea/\nvenv/\n\n# Ignore everything in the generated directory\n/generated/*\n\n# Don't ignore .gitkeep files in the generated directory\n!/generated/.gitkeep\n\nworkspace\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License Copyright (c) 2023 swyx\n\nPermission is hereby granted, free of\ncharge, to any person obtaining a copy of this software and associated\ndocumentation files (the \"Software\"), to deal in the Software without\nrestriction, including without limitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to the\nfollowing conditions:\n\nThe above copyright notice and this permission notice\n(including the next paragraph) shall be included in all copies or substantial\nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\nANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO\nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "Makefile",
    "content": ".PHONY: clean build publish\n\nbuild: clean\n\tpython -m pip install --upgrade --quiet setuptools wheel twine\n\tpython3 -m build\n\t# python setup.py --quiet sdist bdist_wheel\n\npublish: build\n\tpython -m twine check dist/*\n\t# python -m twine upload dist/*\n\tpython3 -m twine upload dist/*\n\nclean:\n\trm -r build dist *.egg-info || true"
  },
  {
    "path": "examples/v1_pong_game/ai.js",
    "content": "// Define AI's paddle speed\nvar aiSpeed = 2;\n\n// Define AI's error rate\nvar aiError = 0.05;\n\n/**\n * Function to make a decision on the direction to move the AI paddle based on the ball's position and the error factor.\n * @param {object} ball - The ball object\n * @param {object} aiPaddle - The AI paddle object\n */\nfunction aiDecision(ball, aiPaddle) {\n    // If ball is above the AI paddle and random number is greater than error rate, move the paddle up\n    if (ball.y < aiPaddle.y && Math.random() > aiError) {\n        aiPaddle.y -= aiSpeed;\n    }\n\n    // If ball is below the AI paddle and random number is greater than error rate, move the paddle down\n    else if (ball.y > aiPaddle.y && Math.random() > aiError) {\n        aiPaddle.y += aiSpeed;\n    }\n}\n"
  },
  {
    "path": "examples/v1_pong_game/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title>Pong Game</title>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\">\n</head>\n<body>\n    <canvas id=\"gameArea\" width=\"400\" height=\"400\"></canvas>\n    <div id=\"playerPaddle\" style=\"height:100px; width:10px; background-color:yellow;\"></div>\n    <div id=\"aiPaddle\" style=\"height:100px; width:10px; background-color:yellow;\"></div>\n\n    <script type=\"text/javascript\" src=\"main.js\"></script>\n    <script type=\"text/javascript\" src=\"ai.js\"></script>\n</body>\n</html>"
  },
  {
    "path": "examples/v1_pong_game/main.js",
    "content": "// Define the canvas, paddles, ball and score\nconst canvas = document.getElementById(\"gameArea\");\nconst ctx = canvas.getContext(\"2d\");\nlet playerPaddle = { x: 0, y: 200, width: 10, height: 100 };\nlet aiPaddle = { x: 390, y: 200, width: 10, height: 100 };\nlet ball = { x: 200, y: 200, radius: 5, dx: 2, dy: 2 };\nlet score = { player: 0, ai: 0 };\n\n// Initialize the game state\nfunction startGame() {\n    playerPaddle.y = 200;\n    aiPaddle.y = 200;\n    ball.x = 200;\n    ball.y = 200;\n    ball.dx = 2;\n    ball.dy = 2;\n    score.player = 0;\n    score.ai = 0;\n}\n\n// Listen for mouse movement to control the player's paddle\ncanvas.addEventListener(\"mousemove\", playerMove);\n\n// Control the player's paddle based on mouse movement\nfunction playerMove(event) {\n    let rect = canvas.getBoundingClientRect();\n    playerPaddle.y = event.clientY - rect.top - playerPaddle.height / 2;\n}\n\n// Control the AI paddle based on the ball's position\nfunction aiMove() {\n    let targetY = ball.y - (aiPaddle.height - ball.radius) / 2;\n    aiPaddle.y += (targetY - aiPaddle.y) * 0.1;\n}\n\n// Check for collisions between the ball and the paddles or the boundaries of the canvas\nfunction checkCollision() {\n    // Ball and player paddle\n    if (ball.y + ball.radius > playerPaddle.y && ball.y - ball.radius < playerPaddle.y + playerPaddle.height && ball.dx < 0) {\n        if (ball.x - ball.radius < playerPaddle.x + playerPaddle.width) {\n            ball.dx *= -1;\n            increaseBallSpeed();\n        }\n    }\n\n    // Ball and AI paddle\n    if (ball.y + ball.radius > aiPaddle.y && ball.y - ball.radius < aiPaddle.y + aiPaddle.height && ball.dx > 0) {\n        if (ball.x + ball.radius > aiPaddle.x) {\n            ball.dx *= -1;\n            increaseBallSpeed();\n        }\n    }\n\n    // Ball and top or bottom\n    if (ball.y + ball.radius > canvas.height || ball.y - ball.radius < 0) {\n        ball.dy *= -1;\n    }\n}\n\n// Update the score based on ball-paddle collisions\nfunction updateScore() {\n    if (ball.x + ball.radius > canvas.width) {\n        score.player++;\n        startGame();\n    } else if (ball.x - ball.radius < 0) {\n        score.ai++;\n        startGame();\n    }\n}\n\n// Increase the ball's speed every time it bounces off a paddle\nfunction increaseBallSpeed() {\n    ball.dx *= 1.1;\n    ball.dy *= 1.1;\n}\n\n// Update the game state at every frame\nfunction updateGame() {\n    // Move the ball\n    ball.x += ball.dx;\n    ball.y += ball.dy;\n\n    // Move the AI paddle\n    aiMove();\n\n    // Check for collisions\n    checkCollision();\n\n    // Update the score\n    updateScore();\n\n    // Render the game state\n    drawGame();\n\n    // Schedule the next update\n    requestAnimationFrame(updateGame);\n}\n\n// Render the game state\nfunction drawGame() {\n    // Clear the canvas\n    ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n    // Draw the paddles\n    ctx.fillStyle = \"yellow\";\n    ctx.fillRect(playerPaddle.x, playerPaddle.y, playerPaddle.width, playerPaddle.height);\n    ctx.fillRect(aiPaddle.x, aiPaddle.y, aiPaddle.width, aiPaddle.height);\n\n    // Draw the ball\n    ctx.beginPath();\n    ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2);\n    ctx.fillStyle = \"red\";\n    ctx.fill();\n\n    // Draw the score\n    ctx.font = \"30px Arial\";\n    ctx.fillText(score.player, 50, 50);\n    ctx.fillText(score.ai, canvas.width - 50, 50);\n}\n\n// Start the game\nstartGame();\nupdateGame();"
  },
  {
    "path": "examples/v1_pong_game/shared_deps.md",
    "content": "Here is a breakdown of the structure of the Pong Game app:\n\n1. **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. \n\n   - *DOM Elements:* \n     - `id=\"gameArea\"` for the canvas.\n     - `id=\"playerPaddle\"` for the player's paddle.\n     - `id=\"aiPaddle\"` for the AI's paddle.\n\n2. **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.\n\n3. **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.\n\n   - *Variables*: \n     - `playerPaddle` and `aiPaddle` objects that represent the player and AI paddles.\n     - `ball` object represents the ball.\n     - `score` object keeps track of the player's and AI's scores.\n   - *Functions*: \n     - `startGame()`: Initializes the game state.\n     - `updateGame()`: Updates the game state at every frame, including moving the paddles and ball and checking for collisions.\n     - `drawGame()`: Renders the game state on the canvas.\n     - `playerMove(event)`: Controls the player's paddle based on mouse movement.\n     - `aiMove()`: Controls the AI paddle based on the ball's position.\n     - `checkCollision()`: Checks for collisions between the ball and the paddles or the boundaries of the canvas.\n     - `updateScore()`: Updates the score based on ball-paddle collisions.\n     - `increaseBallSpeed()`: Increases the ball's speed every time it bounces off a paddle.\n\n4. **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.\n\n   - *Variables*: \n     - `aiSpeed` determines the speed of the AI paddle.\n     - `aiError` determines the probability of error in the AI's movement.\n   - *Functions*: \n     - `aiDecision()`: Makes a decision on the direction to move the AI paddle based on the ball's position and the error factor.\n\nAll 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."
  },
  {
    "path": "examples/v1_pong_game/style.css",
    "content": "/* CSS Styles for the Pong Game App */\n\nbody {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  height: 100vh;\n  background-color: black;\n}\n\n#gameArea {\n  background-color: black;\n  height: 400px;\n  width: 400px;\n  position: relative;\n}\n\n#playerPaddle, #aiPaddle {\n  position: absolute;\n  background-color: yellow;\n  width: 10px;\n  height: 100px;\n}\n\n#playerPaddle {\n  left: 0;\n}\n\n#aiPaddle {\n  right: 0;\n}\n\n.ball {\n  position: absolute;\n  background-color: red;\n  height: 10px;\n  width: 10px;\n  border-radius: 50%;\n}"
  },
  {
    "path": "main.py",
    "content": "import sys\n\nfrom smol_dev.prompts import plan, specify_file_paths, generate_code_sync\nfrom smol_dev.utils import generate_folder, write_file\nfrom smol_dev.main import main\nimport argparse\n\n\n\n\n# for local testing\n# 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\n\nif __name__ == \"__main__\":\n    prompt = \"\"\"\n  a simple JavaScript/HTML/CSS/Canvas app that is a one player game of PONG. \n  The left paddle is controlled by the player, following where the mouse goes.\n  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.\n  Make the canvas a 400 x 400 black square and center it in the app.\n  Make the paddles 100px long, yellow and the ball small and red.\n  Make sure to render the paddles and name them so they can controlled in javascript.\n  Implement the collision detection and scoring as well.\n  Every time the ball bouncess off a paddle, the ball should move faster.\n  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.\n  \"\"\"\n    if len(sys.argv) == 2:\n        prompt = sys.argv[1]\n        args = None\n    else:\n        parser = argparse.ArgumentParser()\n        parser.add_argument(\"--prompt\", type=str, help=\"Prompt for the app to be created.\")\n        parser.add_argument(\"--model\", type=str, default=\"gpt-4-0613\", help=\"model to use. can also use gpt-3.5-turbo-0613\")\n        parser.add_argument(\"--generate_folder_path\", type=str, default=\"generated\", help=\"Path of the folder for generated code.\")\n        parser.add_argument(\"--debug\", type=bool, default=False, help=\"Enable or disable debug mode.\")\n        args = parser.parse_args()\n        if args.prompt:\n            prompt = args.prompt\n\n    # read file from prompt if it ends in a .md filetype\n    if len(prompt) < 100 and prompt.endswith(\".md\"):\n        with open(prompt, \"r\") as promptfile:\n            prompt = promptfile.read()\n\n    print(prompt)\n    \n    if args is None:\n        # This is in case we're just calling the main function directly with a prompt\n        main(prompt=prompt)\n    else:\n        main(prompt=prompt, generate_folder_path=args.generate_folder_path, debug=args.debug, model=args.model)\n"
  },
  {
    "path": "prompt.md",
    "content": "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.\n\n- Only when clicked:\n  - 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 \n  (extracted via an injected content script, and sent over using a `storePageContent` action) \n  - in the background, receives the `storePageContent` data and stores it\n  - only once the new page content is stored, then it pops up a full height window with a minimalistic styled html popup\n  - in the popup script\n    - 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\n      - with the currently fetching page title and a running timer in the center showing time elapsed since call started\n      - do not show it until the api call begins, and hide it when it ends.\n    - 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\n    - check extension storage for an `apiKey`, and if it isn't stored, asks for an API key to Anthropic Claude and stores it.\n    - at the bottom of the popup, show a vertically resizable form that has:\n      - a 2 line textarea with an id and label of `userPrompt`\n        - `userPrompt` has a default value of\n            ```js\n            defaultPrompt = `Please provide a detailed, easy to read HTML summary of the given content`;\n            ```js\n      - a 4 line textarea with an id and label of `stylePrompt`\n        - `stylePrompt` has a default value of\n            ```js\n            defaultStyle = `Respond with 3-4 highlights per section with important keywords, people, numbers, and facts bolded in this HTML format:\n            \n            <h1>{title here}</h1>\n            <h3>{section title here}</h3>\n            <details>\n              <summary>{summary of the section with <strong>important keywords, people, numbers, and facts bolded</strong> and key quotes repeated}</summary>\n              <ul>\n                <li><strong>{first point}</strong>: {short explanation with <strong>important keywords, people, numbers, and facts bolded</strong>}</li>\n                <li><strong>{second point}</strong>: {same as above}</li>\n                <li><strong>{third point}</strong>: {same as above}</li>\n                <!-- a fourth point if warranted -->\n              </ul>\n            </details>\n            <h3>{second section here}</h3>\n            <p>{summary of the section with <strong>important keywords, people, numbers, and facts bolded</strong> and key quotes repeated}</p>\n            <details>\n              <summary>{summary of the section with <strong>important keywords, people, numbers, and facts bolded</strong> and key quotes repeated}</summary>\n              <ul>\n                <!-- as many points as warranted in the same format as above -->\n              </ul>\n            </details>\n            <h3>{third section here}</h3>\n            <!-- and so on, as many sections and details/summary subpoints as warranted -->\n\n            With all the words in brackets replaced by the summary of the content. sanitize non visual HTML tags with HTML entities, so <template> becomes &lt;template&gt; but <strong> 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:\n\n            <hr>\n            <h2>Next prompts</h2>\n            <ul>\n              <li>{question 1}</li>\n              <li>{question 2}</li>\n              <li>{question 3}</li>\n            </ul>`;\n            ```js\n      - and in the last row, on either side,\n        - and a nicely styled submit button with an id of `sendButton` (tactile styling that \"depresses\" on click)\n      - only when `sendButton` is clicked, calls the Anthropic model endpoint https://api.anthropic.com/v1/complete with: \n        - append the page title\n        - append the page content\n        - add the prompt which is a concatenation of\n            ```js\n            finalPrompt = `Human: ${userPrompt} \\n\\n ${stylePrompt} \\n\\n Assistant:`\n            ```\n        - and use the `claude-instant-v1` model (if `pageContent` is <70k words) or the `claude-instant-v1-100k` model (if more) \n        - requesting max tokens = the higher of (25% of the length of the page content, or 750 words)\n        - if another submit event is hit while the previous api call is still inflight, cancel that and start the new one\n    - renders the Anthropic-generated result at the top of the popup in a div with an id of `content`\n\nImportant Details:\n\n- It has to run in a browser environment, so no Nodejs APIs allowed.\n\n- the return signature of the anthropic api is curl https://api.anthropic.com/v1/complete\\\n  -H \"x-api-key: $API_KEY\"\\\n  -H 'content-type: application/json'\\\n  -d '{\n    \"prompt\": \"\\n\\nHuman: Tell me a haiku about trees\\n\\nAssistant: \",\n    \"model\": \"claude-v1\", \"max_tokens_to_sample\": 1000, \"stop_sequences\": [\"\\n\\nHuman:\"]\n  }'\n{\"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}\n\n- 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.\n\n- if the Anthropic api call is a 401, handle that by clearing the stored anthropic api key and asking for it again.\n\n- 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.\n\n- style the popup body with <link rel=\"stylesheet\" href=\"https://unpkg.com/mvp.css@1.12/mvp.css\"> but insist on body margins of 16 and a minimum width of 400 and height of 600.\n\n## debugging notes\n\ninside of background.js, just take the getPageContent response directly\n\n```js\nchrome.runtime.onMessage.addListener((request, sender, sendResponse) => {\n  if (request.action === 'storePageContent') {\n    // dont access request.pageContent\n    chrome.storage.local.set({ pageContent: request }, () => {\n      sendResponse({ success: true });\n    });\n  } else if (request.action === 'getPageContent') {\n    chrome.storage.local.get(['pageContent'], (result) => {\n      // dont access request.pageContent\n      sendResponse(result);\n    });\n  }\n  return true;\n});\n```\n\ninside of popup.js, Update the function calls to `requestAnthropicSummary`\nin `popup.js` to pass the `apiKey`:\n\n```javascript\nchrome.storage.local.get(['apiKey'], (result) => {\n  const apiKey = result.apiKey;\n  requestAnthropicSummary(defaultPrompt, apiKey);\n});\n\nsendButton.addEventListener('click', () => {\n  chrome.storage.local.get(['apiKey'], (result) => {\n    const apiKey = result.apiKey;\n    requestAnthropicSummary(userPrompt.value, apiKey);\n  });\n});\n```\n\nin `popup.js`, store the defaultPrompt at the top level.\nalso, give a HTML format to the anthropic prompt\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[tool.poetry]\nname = \"smol_dev\"\nversion = \"0.0.3\"\ndescription = \"python module of smol developer\"\nauthors = [\"swyx <swyx@dontemail.me>\"]\nlicense = \"MIT\"\nreadme = \"readme.md\"\npackages = [{ include = \"smol_dev\" }]\n\n[tool.poetry.dependencies]\npython = \">=3.10,<4.0.0\"\nopenai = \"^0.27.8\"\nopenai-function-call = \"^0.0.5\"\ntenacity = \"^8.2.2\"\nagent-protocol = \"^1.0.0\"\n\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n\n[tool.poetry.scripts]\nsrc = \"src.__main__:main\"\napi = \"smol_dev.api:main\"\n\n[project.urls]\n\"Homepage\" = \"https://github.com/smol-ai/developer\"\n\"Bug Tracker\" = \"https://github.com/smol-ai/developer/issues\"\n"
  },
  {
    "path": "readme.md",
    "content": "# 🐣 smol developer\n\n<a href=\"https://app.e2b.dev/agent/smol-developer\" target=\"_blank\" rel=\"noopener noreferrer\">\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://app.e2b.dev/api/badge_light\">\n  <img alt=\"Deploy agent on e2b button\" src=\"https://app.e2b.dev/api/badge\"/>\n</picture>\n</a>\n<a href=\"https://github.com/modal-labs/devlooper\"><img src=\"https://github.com/smol-ai/developer/assets/6764957/6af16d37-2494-4722-b3a2-6fc91c005451\"></img>\n</a>\n<a href=\"https://twitter.com/morph_labs/status/1689321673151979536\"><img src=\"https://avatars.githubusercontent.com/u/136536927?s=40&v=4\" alt=\"Morph\"></img> Morph\n</a>\n\n***Human-centric & Coherent Whole Program Synthesis*** aka your own personal junior developer\n\n> [Build the thing that builds the thing!](https://twitter.com/swyx/status/1657578738345979905) a `smol dev` for every dev in every situation\n\nThis is a \"junior developer\" agent (aka `smol dev`) that either:\n\n1. scaffolds an entire codebase out for you once you give it a product spec\n2. gives you basic building blocks to have a smol developer inside of your own app.\n\nInstead 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.\n\nAfter 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!\n\n## Basic Usage\n\n### In Git Repo mode\n\n```bash\n# install\ngit clone https://github.com/smol-ai/developer.git\ncd developer\npoetry install # install dependencies. pip install poetry if you need\n\n# run\npython main.py \"a HTML/JS/CSS Tic Tac Toe Game\" # defaults to gpt-4-0613\n# python main.py \"a HTML/JS/CSS Tic Tac Toe Game\" --model=gpt-3.5-turbo-0613\n\n# other cli flags\npython main.py --prompt prompt.md # for longer prompts, move them into a markdown file\npython main.py --prompt prompt.md --debug True # for debugging\n```\n\n<details>\n  <summary>\nThis lets you develop apps as a human in the loop, as per the original version of smol developer.\n  </summary>\n\n\n<p align=\"center\">\n  <img height=200 src=\"https://pbs.twimg.com/media/FwEzVCcaMAE7t4h?format=jpg&name=large\" />\n</p>\n\n*engineering with prompts, rather than prompt engineering*\n\nThe demo example in `prompt.md` shows the potential of AI-enabled, but still firmly human developer centric, workflow:\n\n- Human writes a basic prompt for the app they want to build\n- `main.py` generates code\n- Human runs/reads the code\n- Human can:\n  - simply add to the prompt as they discover underspecified parts of the prompt\n  - manually runs the code and identifies errors\n  - *paste the error into the prompt* just like they would file a GitHub issue\n  - for extra help, they can use `debugger.py` which reads the whole codebase to make specific code change suggestions\n\nLoop 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*)\n\n</details>\n\nIn this way you can use your clone of this repo itself to prototype/develop your app.\n\n### In Library mode\n\nThis is the new thing in smol developer v1! Add `smol developer` to your own projects!\n\n```bash\npip install smol_dev\n```\n\nHere 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:\n\n```python\nfrom smol_dev.prompts import plan, specify_file_paths, generate_code_sync\n\nprompt = \"a HTML/JS/CSS Tic Tac Toe Game\"\n\nshared_deps = plan(prompt) # returns a long string representing the coding plan\n\n# do something with the shared_deps plan if you wish, for example ask for user confirmation/edits and iterate in a loop\n\nfile_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.\n\n# do something with the filepaths if you wish, for example display a plan\n\n# loop through file_paths array and generate code for each file\nfor file_path in file_paths:\n    code = generate_code_sync(prompt, shared_deps, file_path) # generates the source code of each file\n\n    # do something with the source code of the file, eg. write to disk or display in UI\n    # there is also an async `generate_code()` version of this\n```\n\n### In API mode (via [Agent Protocol](https://github.com/e2b-dev/agent-protocol))\nTo start the server run:\n```bash\npoetry run api\n```\nor\n```bash\npython smol_dev/api.py\n```\n\nand then you can call the API using either the following commands:\n\nTo **create a task** run:\n```bash\ncurl --request POST \\\n  --url http://localhost:8000/agent/tasks \\\n  --header 'Content-Type: application/json' \\\n  --data '{\n\t\"input\": \"Write simple script in Python. It should write '\\''Hello world!'\\'' to hi.txt\"\n}'\n```\n\nYou will get a response like this:\n```json\n{\"input\":\"Write simple script in Python. It should write 'Hello world!' to hi.txt\",\"task_id\":\"d2c4e543-ae08-4a97-9ac5-5f9a4459cb19\",\"artifacts\":[]}\n```\n\nThen to **execute one step of the task** copy the `task_id` you got from the previous request and run:\n\n```bash\ncurl --request POST \\\n  --url http://localhost:8000/agent/tasks/<task-id>/steps\n```\n\nor you can use [Python client library](https://github.com/e2b-dev/agent-protocol/tree/main/agent_client/python):\n\n```python\nfrom agent_protocol_client import AgentApi, ApiClient, TaskRequestBody\n\n...\n\nprompt = \"Write simple script in Python. It should write 'Hello world!' to hi.txt\"\n\nasync with ApiClient() as api_client:\n    # Create an instance of the API class\n    api_instance = AgentApi(api_client)\n    task_request_body = TaskRequestBody(input=prompt)\n\n    task = await api_instance.create_agent_task(\n        task_request_body=task_request_body\n    )\n    task_id = task.task_id\n    response = await api_instance.execute_agent_task_step(task_id=task_id)\n\n...\n\n```\n\n## examples/prompt gallery\n\n- [6 minute video demo](https://youtu.be/UCo7YeTy-aE) - (sorry for sped up audio, we were optimizing for twitter, bad call)\n  - 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\n  - 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)\n- `smol-plugin` - prompt to ChatGPT plugin ([tweet](https://twitter.com/ultrasoundchad/status/1659366507409985536?s=20), [fork](https://github.com/gmchad/smol-plugin))\n\n  <img src=\"https://github.com/smol-ai/developer/assets/6764957/6ffaac3b-5d90-460a-a590-c8a8c004bd36\" height=200 />\n\n- [Prompt to Pokemon App](https://twitter.com/RobertCaracaus/status/1659312419485761536?s=20)\n\n  <img src=\"https://github.com/smol-ai/developer/assets/6764957/15fa189a-3f52-4618-ac8e-2a77b6500264\" height=200 />\n\n- [Political Campaign CRM Program example](https://github.com/smol-ai/developer/pull/22/files)\n- [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))\n- [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\n\n  <img src=\"https://github.com/smol-ai/developer/assets/6764957/e80058f1-ea9c-42dd-87ff-004b61f08f2e\" height=200 />\n\n- [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\n\n  <img src=\"https://github.com/smol-ai/developer/assets/6764957/c51f9f8c-021d-446a-b44d-7a6f48e64550\" height=200 />\n\nI'm actively seeking more examples, please PR yours!\n\nsorry for the lack of examples, I know that is frustrating but I wasnt ready for so many of you lol\n\n## major forks/alternatives\n\nplease send in alternative implementations, and deploy strategies on alternative stacks!\n\n- **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)\n- **C#/Dotnet**: https://github.com/colhountech/smol-ai-dotnet in C#!\n- **Golang**: https://github.com/tmc/smol-dev-go in Go\n- https://github.com/gmchad/smol-plugin automatically generate @openai plugins by specifying your API in markdown in smol-developer style\n- your fork here!\n\n\n### innovations and insights\n\n> Please subscribe to https://latent.space/ for a fuller writeup and insights and reflections\n\n- **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)\n  - turns out you can specify prompts in code in prompts and gpt4 obeys that to the letter\n- **Copy and paste programming**\n  - 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\n  - 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\".\n- **Debugging by `cat`ing** the whole codebase with your error message and getting specific fix suggestions - particularly delightful!\n- **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.\n  - 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...\n  - ... 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.\n  - see `prompt.md` for SOTA smol-dev prompting\n- **Low activation energy for unfamiliar APIs**\n  - 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.\n  - 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\n  - 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.\n- **Modal is all you need** - we chose Modal to solve 4 things:\n  - solve python dependency hell in dev and prod\n  - parallelizable code generation\n  - simple upgrade path from local dev to cloud hosted endpoints (in future)\n  - fault tolerant openai api calls with retries/backoff, and attached storage (for future use)\n\n> Please subscribe to https://latent.space/ for a fuller writeup and insights and reflections\n\n### caveats\n\nWe 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.\n\nWe dont have access to GPT4-32k, but if we did, we'd explore dumping entire API/SDK documentation into context.\n\nThe 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).\n\n\n## future directions\n\nthings to try/would accept open issue discussions and PRs:\n\n- **specify .md files for each generated file**, with further prompts that could finetune the output in each of them\n  - so basically like `popup.html.md` and `content_script.js.md` and so on\n- **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\n  - 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\n- **ability to install its own dependencies**\n  - 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)?\n  - Modal has an interesting possibility: generate functions that speak modal which also solves the dependency thing https://twitter.com/akshat_b/status/1658146096902811657\n- **self-heal** by running the code itself and use errors as information for reprompting\n  - however its a bit hard to get errors from the chrome extension environment so we did not try this\n- **using anthropic as the coding layer**\n  - you can run `modal run anthropic.py --prompt prompt.md --outputdir=anthropic` to try it\n  - but it doesnt work because anthropic doesnt follow instructions to generate file code very well.\n- **make agents that autonomously run this code in a loop/watch the prompt file** and regenerate code each time, on a new git branch\n  - the code could be generated on 5 simultaneous git branches and checking their output would just involve switching git branches\n"
  },
  {
    "path": "smol_dev/__init__.py",
    "content": "from smol_dev.prompts import *\n__author__ = \"morph\"\n"
  },
  {
    "path": "smol_dev/api.py",
    "content": "import enum\nimport os\nfrom pathlib import Path\n\nfrom smol_dev.prompts import plan, specify_file_paths, generate_code\nfrom smol_dev.utils import write_file\n\nfrom agent_protocol import Agent, Step, Task\n\n\nclass StepTypes(str, enum.Enum):\n    PLAN = \"plan\"\n    SPECIFY_FILE_PATHS = \"specify_file_paths\"\n    GENERATE_CODE = \"generate_code\"\n\n\nasync def _generate_shared_deps(step: Step) -> Step:\n    task = await Agent.db.get_task(step.task_id)\n    shared_deps = plan(task.input)\n    await Agent.db.create_step(\n        step.task_id,\n        StepTypes.SPECIFY_FILE_PATHS,\n        additional_properties={\n            \"shared_deps\": shared_deps,\n        },\n    )\n    step.output = shared_deps\n    return step\n\n\nasync def _generate_file_paths(task: Task, step: Step) -> Step:\n    shared_deps = step.additional_properties[\"shared_deps\"]\n    file_paths = specify_file_paths(task.input, shared_deps)\n    for file_path in file_paths[:-1]:\n        await Agent.db.create_step(\n            task.task_id,\n            f\"Generate code for {file_path}\",\n            additional_properties={\n                \"shared_deps\": shared_deps,\n                \"file_path\": file_paths[-1],\n            },\n        )\n\n    await Agent.db.create_step(\n        task.task_id,\n        f\"Generate code for {file_paths[-1]}\",\n        is_last=True,\n        additional_properties={\n            \"shared_deps\": shared_deps,\n            \"file_path\": file_paths[-1],\n        },\n    )\n\n    step.output = f\"File paths are: {str(file_paths)}\"\n    return step\n\n\nasync def _generate_code(task: Task, step: Step) -> Step:\n    shared_deps = step.additional_properties[\"shared_deps\"]\n    file_path = step.additional_properties[\"file_path\"]\n\n    code = await generate_code(task.input, shared_deps, file_path)\n    step.output = code\n\n    write_file(os.path.join(Agent.get_workspace(task.task_id), file_path), code)\n    path = Path(\"./\" + file_path)\n    await Agent.db.create_artifact(\n        task_id=task.task_id,\n        step_id=step.step_id,\n        relative_path=str(path.parent),\n        file_name=path.name,\n    )\n\n    return step\n\n\nasync def task_handler(task: Task) -> None:\n    if not task.input:\n        raise Exception(\"No task prompt\")\n    await Agent.db.create_step(task.task_id, StepTypes.PLAN)\n\n\nasync def step_handler(step: Step):\n    task = await Agent.db.get_task(step.task_id)\n    if step.name == StepTypes.PLAN:\n        return await _generate_shared_deps(step)\n    elif step.name == StepTypes.SPECIFY_FILE_PATHS:\n        return await _generate_file_paths(task, step)\n    else:\n        return await _generate_code(task, step)\n\n\nAgent.setup_agent(task_handler, step_handler).start()\n"
  },
  {
    "path": "smol_dev/main.py",
    "content": "import sys\nimport time\n\nfrom smol_dev.prompts import plan, specify_file_paths, generate_code_sync\nfrom smol_dev.utils import generate_folder, write_file\nimport argparse\n\n# model = \"gpt-3.5-turbo-0613\"\ndefaultmodel = \"gpt-4-0613\"\n\ndef main(prompt, generate_folder_path=\"generated\", debug=False, model: str = defaultmodel):\n    # create generateFolder folder if doesnt exist\n    generate_folder(generate_folder_path)\n\n    # plan shared_deps\n    if debug:\n        print(\"--------shared_deps---------\")\n    with open(f\"{generate_folder_path}/shared_deps.md\", \"wb\") as f:\n\n        start_time = time.time()\n        def stream_handler(chunk):\n            f.write(chunk)\n            if debug:\n                end_time = time.time()\n\n                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)))\n                sys.stdout.flush()\n                stream_handler.count += len(chunk)\n\n        stream_handler.count = 0\n        stream_handler.onComplete = lambda x: sys.stdout.write(\"\\033[0m\\n\") # remove the stdout line when streaming is complete\n\n        shared_deps = plan(prompt, stream_handler, model=model)\n    if debug:\n        print(shared_deps)\n    write_file(f\"{generate_folder_path}/shared_deps.md\", shared_deps)\n    if debug:\n        print(\"--------shared_deps---------\")\n\n    # specify file_paths\n    if debug:\n        print(\"--------specify_filePaths---------\")\n    file_paths = specify_file_paths(prompt, shared_deps, model=model)\n    if debug:\n        print(file_paths)\n    if debug:\n        print(\"--------file_paths---------\")\n\n    # loop through file_paths array and generate code for each file\n    for file_path in file_paths:\n        file_path = f\"{generate_folder_path}/{file_path}\"  # just append prefix\n        if debug:\n            print(f\"--------generate_code: {file_path} ---------\")\n\n        start_time = time.time()\n        def stream_handler(chunk):\n            if debug:\n                end_time = time.time()\n                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)))\n                sys.stdout.flush()\n                stream_handler.count += len(chunk)\n        stream_handler.count = 0\n        stream_handler.onComplete = lambda x: sys.stdout.write(\"\\033[0m\\n\") # remove the stdout line when streaming is complete\n        code = generate_code_sync(prompt, shared_deps, file_path, stream_handler, model=model)\n        if debug:\n            print(code)\n        if debug:\n            print(f\"--------generate_code: {file_path} ---------\")\n        # create file with code content\n        write_file(file_path, code)\n        \n    print(\"--------smol dev done!---------\")\n\n\n# for local testing\n# 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\n\nif __name__ == \"__main__\":\n    prompt = \"\"\"\n  a simple JavaScript/HTML/CSS/Canvas app that is a one player game of PONG. \n  The left paddle is controlled by the player, following where the mouse goes.\n  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.\n  Make the canvas a 400 x 400 black square and center it in the app.\n  Make the paddles 100px long, yellow and the ball small and red.\n  Make sure to render the paddles and name them so they can controlled in javascript.\n  Implement the collision detection and scoring as well.\n  Every time the ball bouncess off a paddle, the ball should move faster.\n  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.\n  \"\"\"\n    if len(sys.argv) == 2:\n        prompt = sys.argv[1]\n    else:\n        \n        parser = argparse.ArgumentParser()\n        parser.add_argument(\"--prompt\", type=str, required=True, help=\"Prompt for the app to be created.\")\n        parser.add_argument(\"--generate_folder_path\", type=str, default=\"generated\", help=\"Path of the folder for generated code.\")\n        parser.add_argument(\"--debug\", type=bool, default=False, help=\"Enable or disable debug mode.\")\n        args = parser.parse_args()\n        if args.prompt:\n            prompt = args.prompt\n        \n    print(prompt)\n        \n    main(prompt=prompt, generate_folder_path=args.generate_folder_path, debug=args.debug)\n"
  },
  {
    "path": "smol_dev/prompts.py",
    "content": "import asyncio\nimport re\nimport time\nfrom typing import List, Optional, Callable, Any\n\nimport openai\nfrom openai_function_call import openai_function\nfrom tenacity import (\n    retry,\n    stop_after_attempt,\n    wait_random_exponential,\n)\nimport logging\n\nlogger = logging.getLogger(__name__)\n\n\nSMOL_DEV_SYSTEM_PROMPT = \"\"\"\nYou are a top tier AI developer who is trying to write a program that will generate code for the user based on their intent.\nDo not leave any todos, fully implement every feature requested.\n\nWhen 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.\n\"\"\"\n\n\n@openai_function\ndef file_paths(files_to_edit: List[str]) -> List[str]:\n    \"\"\"\n    Construct a list of strings.\n    \"\"\"\n    # print(\"filesToEdit\", files_to_edit)\n    return files_to_edit\n\n\ndef specify_file_paths(prompt: str, plan: str, model: str = 'gpt-3.5-turbo-0613'):\n    completion = openai.ChatCompletion.create(\n        model=model,\n        temperature=0.7,\n        functions=[file_paths.openai_schema],\n        function_call={\"name\": \"file_paths\"},\n        messages=[\n            {\n                \"role\": \"system\",\n                \"content\": f\"\"\"{SMOL_DEV_SYSTEM_PROMPT}\n      Given the prompt and the plan, return a list of strings corresponding to the new files that will be generated.\n                  \"\"\",\n            },\n            {\n                \"role\": \"user\",\n                \"content\": f\"\"\" I want a: {prompt} \"\"\",\n            },\n            {\n                \"role\": \"user\",\n                \"content\": f\"\"\" The plan we have agreed on is: {plan} \"\"\",\n            },\n        ],\n    )\n    result = file_paths.from_response(completion)\n    return result\n\n\ndef plan(prompt: str, stream_handler: Optional[Callable[[bytes], None]] = None, model: str='gpt-3.5-turbo-0613', extra_messages: List[Any] = []):\n    completion = openai.ChatCompletion.create(\n        model=model,\n        temperature=0.7,\n        stream=True,\n        messages=[\n            {\n                \"role\": \"system\",\n                \"content\": f\"\"\"{SMOL_DEV_SYSTEM_PROMPT}\n\n    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.\n  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.\n                Respond only with plans following the above schema.\n                  \"\"\",\n            },\n            {\n                \"role\": \"user\",\n                \"content\": f\"\"\" the app prompt is: {prompt} \"\"\",\n            },\n            *extra_messages,\n        ],\n    )\n\n    collected_messages = []\n    for chunk in completion:\n        chunk_message_dict = chunk[\"choices\"][0]\n        chunk_message = chunk_message_dict[\"delta\"]  # extract the message\n        if chunk_message_dict[\"finish_reason\"] is None:\n            collected_messages.append(chunk_message)  # save the message\n            if stream_handler:\n                try:\n                    stream_handler(chunk_message[\"content\"].encode(\"utf-8\"))\n                except Exception as err:\n                    logger.info(\"\\nstream_handler error:\", err)\n                    logger.info(chunk_message)\n    # if stream_handler and hasattr(stream_handler, \"onComplete\"): stream_handler.onComplete('done')\n    full_reply_content = \"\".join([m.get(\"content\", \"\") for m in collected_messages])\n    return full_reply_content\n\n\n@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))\nasync def generate_code(prompt: str, plan: str, current_file: str, stream_handler: Optional[Callable[Any, Any]] = None,\n                        model: str = 'gpt-3.5-turbo-0613') -> str:\n    first = True\n    chunk_count = 0\n    start_time = time.time()\n    completion = openai.ChatCompletion.acreate(\n        model=model,\n        temperature=0.7,\n        messages=[\n            {\n                \"role\": \"system\",\n                \"content\": f\"\"\"{SMOL_DEV_SYSTEM_PROMPT}\n\n  In response to the user's prompt,\n  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.\n\n  We have broken up the program into per-file generation.\n  Now your job is to generate only the code for the file: {current_file}\n\n  only write valid code for the given filepath and file type, and return only the code.\n  do not add any other explanation, only return valid code for that file type.\n                  \"\"\",\n            },\n            {\n                \"role\": \"user\",\n                \"content\": f\"\"\" the plan we have agreed on is: {plan} \"\"\",\n            },\n            {\n                \"role\": \"user\",\n                \"content\": f\"\"\" the app prompt is: {prompt} \"\"\",\n            },\n            {\n                \"role\": \"user\",\n                \"content\": f\"\"\"\n    Make sure to have consistent filenames if you reference other files we are also generating.\n\n    Remember that you must obey 3 things:\n       - you are generating code for the file {current_file}\n       - do not stray from the names of the files and the plan we have decided on\n       - MOST IMPORTANT OF ALL - every line of code you generate must be valid code. Do not include code fences in your response, for example\n\n    Bad response (because it contains the code fence):\n    ```javascript\n    console.log(\"hello world\")\n    ```\n\n    Good response (because it only contains the code):\n    console.log(\"hello world\")\n\n    Begin generating the code now.\n\n    \"\"\",\n            },\n        ],\n        stream=True,\n    )\n\n    collected_messages = []\n    async for chunk in await completion:\n        chunk_message_dict = chunk[\"choices\"][0]\n        chunk_message = chunk_message_dict[\"delta\"]  # extract the message\n        if chunk_message_dict[\"finish_reason\"] is None:\n            collected_messages.append(chunk_message)  # save the message\n            if stream_handler:\n                try:\n                    stream_handler(chunk_message[\"content\"].encode(\"utf-8\"))\n                except Exception as err:\n                    logger.info(\"\\nstream_handler error:\", err)\n                    logger.info(chunk_message)\n\n    # if stream_handler and hasattr(stream_handler, \"onComplete\"): stream_handler.onComplete('done')\n    code_file = \"\".join([m.get(\"content\", \"\") for m in collected_messages])\n\n    pattern = r\"```[\\w\\s]*\\n([\\s\\S]*?)```\"  # codeblocks at start of the string, less eager\n    code_blocks = re.findall(pattern, code_file, re.MULTILINE)\n    return code_blocks[0] if code_blocks else code_file\n\n\ndef generate_code_sync(prompt: str, plan: str, current_file: str,\n                       stream_handler: Optional[Callable[Any, Any]] = None,\n                       model: str = 'gpt-3.5-turbo-0613') -> str:\n    loop = asyncio.get_event_loop()\n    return loop.run_until_complete(generate_code(prompt, plan, current_file, stream_handler, model))\n"
  },
  {
    "path": "smol_dev/utils.py",
    "content": "import os\nimport shutil\n\n\ndef generate_folder(folder_path: str):\n    if not os.path.exists(folder_path):\n        os.makedirs(folder_path)\n    else:\n        shutil.rmtree(folder_path)\n        os.makedirs(folder_path)\n\n\ndef write_file(file_path: str, content: str):\n    # if filepath doesn't exist, create it\n    if not os.path.exists(os.path.dirname(file_path)):\n        os.makedirs(os.path.dirname(file_path))\n    with open(file_path, \"w\") as f:\n        f.write(content)\n"
  },
  {
    "path": "v0/code2prompt/code2prompt-gpt3.md",
    "content": "The program is a Chrome extension that summarizes web pages using the Anthropic Claude API. It consists of several files: popup.js, styles.css, \nbackground.js, popup.html, shared_dependencies.md, and content_script.js. \n\npopup.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\nfor the form, including retrieving the user's input, calling the Anthropic API, and rendering the summary in the content div. styles.css provides \nstyling for the UI elements.\n\nbackground.js is responsible for executing content_script.js, which retrieves the page content (title and body text) and sends it to popup.js for \nprocessing. It also handles storing and retrieving the page content data using Chrome's storage API.\n\nshared_dependencies.md lists the shared variables, data schemas, DOM element IDs, message names, and function names used across the various files.\n\nOverall, the program uses a combination of JavaScript, HTML, and CSS to provide a user-friendly interface for summarizing web pages using the \nAnthropic Claude API."
  },
  {
    "path": "v0/code2prompt/code2prompt-gpt4.md",
    "content": "# Anthropic Claude Summary Extension\n\nA Chrome extension that summarizes web pages using the \nAnthropic Claude API.\n\n## Files\n\n### popup.js\n\nThis file contains the main logic for the popup window of the\nextension. It listens for the DOMContentLoaded event and \ninitializes the following DOM elements:\n\n- `userPrompt`: A textarea for the user to input a prompt.\n- `stylePrompt`: A textarea for the user to input a style \nprompt.\n- `maxTokens`: An input field for the user to set the maximum\nnumber of tokens for the summary.\n- `sendButton`: A button to submit the form and request a \nsummary.\n- `content`: A div to display the summary.\n- `loadingIndicator`: A div to display a loading indicator \nwhile waiting for the summary.\n\nThe `sendButton` has a click event listener that calls the \n`requestAnthropicSummary` function. This function sends a \nmessage to the background script to get the page content, \nconstructs a prompt using the user input and page content, \nand sends a POST request to the Anthropic Claude API. The \nresponse is then displayed in the `content` div.\n\n### styles.css\n\nThis file contains the CSS styles for the popup window. It \ndefines styles for the body, textarea, input, button, \ncontent, and loadingIndicator elements.\n\n### background.js\n\nThis file contains the background script for the extension. \nIt listens for the following events:\n\n- `chrome.action.onClicked`: Executes the content_script.js \nfile on the active tab when the extension icon is clicked.\n- `chrome.runtime.onMessage`: Listens for messages with the \nfollowing actions:\n  - `storePageContent`: Stores the page title and content in \nthe local storage using the tab ID as the key.\n  - `getPageContent`: Retrieves the page title and content \nfrom the local storage using the tab ID and sends it as a \nresponse.\n\n### popup.html\n\nThis file contains the HTML structure for the popup window. \nIt includes the following elements:\n\n- A form with labels and inputs for userPrompt, stylePrompt, \nand maxTokens.\n- A submit button with the ID \"sendButton\".\n- A div with the ID \"content\" to display the summary.\n- A div with the ID \"loadingIndicator\" to display a loading \nindicator.\n\nThe file also includes a link to the styles.css file and a \nscript tag for the popup.js file.\n\n### shared_dependencies.md\n\nThis file lists the shared dependencies, variables, DOM \nelement IDs, message names, and function names used in the \nextension.\n\n### content_script.js\n\nThis file contains a function called `storePageContent` that \nretrieves the page title and content and sends a message to \nthe background script with the action \"storePageContent\" and \nthe data as an object containing the title and content.\n\nIt also listens for messages with the action \n\"storePageContent\" and calls the `storePageContent` function \nwhen received.\n\n### manifest.json\n\nThis file contains the manifest for the extension. It \nincludes the following properties:\n\n- `manifest_version`: Set to 2.\n- `name`: Set to \"Anthropic Claude Summary Extension\".\n- `version`: Set to \"1.0\".\n- `description`: Set to \"A Chrome extension that summarizes \nweb pages using the Anthropic Claude API.\"\n- `permissions`: Includes \"activeTab\" and \"storage\".\n- `action`: Defines the default_popup, and default_icon for \nthe extension.\n- `background`: Includes the background.js script and sets \nthe \"persistent\" property to false.\n- `content_scripts`: Includes the content_script.js file and \nmatches all URLs.\n- `icons`: Defines the icons for the extension.\n\n**Note**: Ensure that all the IDs of the DOM elements and the\ndata structure of `pageContent` referenced/shared by the \nJavaScript files match up exactly. Use only Chrome Manifest \nV3 APIs. Rename the extension to \"code2prompt2code\"."
  },
  {
    "path": "v0/code2prompt.py",
    "content": "import modal\nimport os\nfrom constants import DEFAULT_DIR, DEFAULT_MODEL, DEFAULT_MAX_TOKENS, EXTENSION_TO_SKIP\n\nstub = modal.Stub(\"smol-codetoprompt-v1\")\nopenai_image = modal.Image.debian_slim().pip_install(\"openai\")\n\n\n\ndef read_file(filename):\n    with open(filename, 'r') as file:\n        return file.read()\n\ndef walk_directory(directory):\n    code_contents = {}\n    for root, dirs, files in os.walk(directory):\n        for file in files:\n            if not any(file.endswith(ext) for ext in EXTENSION_TO_SKIP):\n                try:\n                    relative_filepath = os.path.relpath(os.path.join(root, file), directory)\n                    code_contents[relative_filepath] = read_file(os.path.join(root, file))\n                except Exception as e:\n                    code_contents[relative_filepath] = f\"Error reading file {file}: {str(e)}\"\n    return code_contents\n\n\n\n@stub.local_entrypoint()\ndef main(prompt=None, directory=DEFAULT_DIR, model=DEFAULT_MODEL):\n  code_contents = walk_directory(directory)\n\n  # Now, `code_contents` is a dictionary that contains the content of all your non-image files\n  # You can send this to OpenAI's text-davinci-003 for help\n\n  context = \"\\n\".join(f\"{path}:\\n{contents}\" for path, contents in code_contents.items())\n  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:\"\n  prompt = \"My files are as follows: \" + context + \"\\n\\n\" + ((\"Take special note of the following: \" + prompt) if prompt else \"\")\n  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.\"\n  res = generate_response.call(system, prompt, model)\n  # print res in teal\n  print(\"\\033[96m\" + res + \"\\033[0m\")\n\n\n@stub.function(\n    image=openai_image,\n    secret=modal.Secret.from_dotenv(),\n    retries=modal.Retries(\n        max_retries=3,\n        backoff_coefficient=2.0,\n        initial_delay=1.0,\n    ),\n    concurrency_limit=5,\n    timeout=120,\n)\ndef generate_response(system_prompt, user_prompt, model=DEFAULT_MODEL, *args):\n    import openai\n\n    # Set up your OpenAI API credentials\n    openai.api_key = os.environ[\"OPENAI_API_KEY\"]\n\n    messages = []\n    messages.append({\"role\": \"system\", \"content\": system_prompt})\n    messages.append({\"role\": \"user\", \"content\": user_prompt})\n    # loop thru each arg and add it to messages alternating role between \"assistant\" and \"user\"\n    role = \"assistant\"\n    for value in args:\n        messages.append({\"role\": role, \"content\": value})\n        role = \"user\" if role == \"assistant\" else \"assistant\"\n\n    params = {\n        'model': model,\n        \"messages\": messages,\n        \"max_tokens\": 2500,\n        \"temperature\": 0,\n    }\n\n    # Send the API request\n    response = openai.ChatCompletion.create(**params)\n\n    # Get the reply from the API response\n    reply = response.choices[0][\"message\"][\"content\"]\n    return reply"
  },
  {
    "path": "v0/code2prompt2code/background.js",
    "content": "chrome.action.onClicked.addListener((tab) => {\n  chrome.scripting.executeScript({\n    target: { tabId: tab.id },\n    files: [\"content_script.js\"],\n  });\n});\n\nchrome.runtime.onMessage.addListener((request, sender, sendResponse) => {\n  if (request.action === \"storePageContent\") {\n    const tabId = sender.tab.id;\n    chrome.storage.local.set({ [tabId]: request.data }, () => {\n      sendResponse({ success: true });\n    });\n    return true;\n  } else if (request.action === \"getPageContent\") {\n    const tabId = sender.tab.id;\n    chrome.storage.local.get(tabId, (data) => {\n      sendResponse(data[tabId]);\n    });\n    return true;\n  }\n});"
  },
  {
    "path": "v0/code2prompt2code/content_script.js",
    "content": "function storePageContent() {\n  const pageTitle = document.title;\n  const pageContent = document.body.innerText;\n\n  chrome.runtime.sendMessage({\n    action: \"storePageContent\",\n    data: { title: pageTitle, content: pageContent },\n  });\n}\n\nchrome.runtime.onMessage.addListener((request, sender, sendResponse) => {\n  if (request.action === \"storePageContent\") {\n    storePageContent();\n  }\n});"
  },
  {
    "path": "v0/code2prompt2code/manifest.json",
    "content": "{\n  \"manifest_version\": 3,\n  \"name\": \"code2prompt2code\",\n  \"version\": \"1.0\",\n  \"description\": \"A Chrome extension that summarizes web pages using the Anthropic Claude API.\",\n  \"permissions\": [\"activeTab\", \"storage\"],\n  \"action\": {\n    \"default_popup\": \"popup.html\",\n    \"default_icon\": {\n      \"16\": \"icons/icon16.png\",\n      \"48\": \"icons/icon48.png\",\n      \"128\": \"icons/icon128.png\"\n    }\n  },\n  \"background\": {\n    \"service_worker\": \"background.js\",\n    \"type\": \"module\"\n  },\n  \"content_scripts\": [\n    {\n      \"matches\": [\"<all_urls>\"],\n      \"js\": [\"content_script.js\"]\n    }\n  ],\n  \"icons\": {\n    \"16\": \"icons/icon16.png\",\n    \"48\": \"icons/icon48.png\",\n    \"128\": \"icons/icon128.png\"\n  }\n}"
  },
  {
    "path": "v0/code2prompt2code/popup.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Anthropic Claude Summary Extension</title>\n    <link rel=\"stylesheet\" href=\"styles.css\">\n</head>\n<body>\n    <form>\n        <label for=\"userPrompt\">User Prompt:</label>\n        <textarea id=\"userPrompt\"></textarea>\n        <label for=\"stylePrompt\">Style Prompt:</label>\n        <textarea id=\"stylePrompt\"></textarea>\n        <label for=\"maxTokens\">Max Tokens:</label>\n        <input type=\"number\" id=\"maxTokens\">\n        <button type=\"button\" id=\"sendButton\">Request Summary</button>\n    </form>\n    <div id=\"content\"></div>\n    <div id=\"loadingIndicator\" style=\"display:none;\"></div>\n    <script src=\"popup.js\"></script>\n</body>\n</html>"
  },
  {
    "path": "v0/code2prompt2code/popup.js",
    "content": "document.addEventListener(\"DOMContentLoaded\", () => {\n  const userPrompt = document.getElementById(\"userPrompt\");\n  const stylePrompt = document.getElementById(\"stylePrompt\");\n  const maxTokens = document.getElementById(\"maxTokens\");\n  const sendButton = document.getElementById(\"sendButton\");\n  const content = document.getElementById(\"content\");\n  const loadingIndicator = document.getElementById(\"loadingIndicator\");\n\n  sendButton.addEventListener(\"click\", requestAnthropicSummary);\n\n  async function requestAnthropicSummary() {\n    loadingIndicator.style.display = \"block\";\n    content.innerHTML = \"\";\n\n    const tab = await new Promise(resolve => chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => resolve(tab)));\n    chrome.runtime.sendMessage({ action: \"getPageContent\", tabId: tab.id }, async (pageContent) => {\n      const prompt = `${userPrompt.value}\\n\\n${pageContent.title}\\n\\n${pageContent.content}`;\n      const style = stylePrompt.value;\n      const tokens = parseInt(maxTokens.value, 10);\n\n      const response = await fetch(\"https://api.anthropic.com/claude\", {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\"\n        },\n        body: JSON.stringify({ prompt, style, tokens })\n      });\n\n      const summary = await response.json();\n      content.innerHTML = summary;\n      loadingIndicator.style.display = \"none\";\n    });\n  }\n});"
  },
  {
    "path": "v0/code2prompt2code/shared_dependencies.md",
    "content": "## Shared dependencies:\n\n1. DOM element IDs:\n   - userPrompt\n   - stylePrompt\n   - maxTokens\n   - sendButton\n   - content\n   - loadingIndicator\n\n2. Message names:\n   - storePageContent\n   - getPageContent\n\n3. Function names:\n   - requestAnthropicSummary\n   - storePageContent\n\n4. Data schemas:\n   - pageContent: { title: string, content: string }"
  },
  {
    "path": "v0/code2prompt2code/styles.css",
    "content": "body {\n  font-family: Arial, sans-serif;\n  margin: 0;\n  padding: 15px;\n}\n\ntextarea, input {\n  display: block;\n  width: 100%;\n  margin-bottom: 10px;\n  padding: 5px;\n  font-size: 14px;\n}\n\nbutton {\n  background-color: #4CAF50;\n  border: none;\n  color: white;\n  padding: 10px 20px;\n  text-align: center;\n  text-decoration: none;\n  display: inline-block;\n  font-size: 14px;\n  margin: 10px 0;\n  cursor: pointer;\n}\n\nbutton:hover {\n  background-color: #45a049;\n}\n\n#content {\n  margin-top: 20px;\n  font-size: 14px;\n  line-height: 1.5;\n}\n\n#loadingIndicator {\n  display: none;\n  font-size: 14px;\n  color: #999;\n}"
  },
  {
    "path": "v0/constants.py",
    "content": "EXTENSION_TO_SKIP = [\".png\",\".jpg\",\".jpeg\",\".gif\",\".bmp\",\".svg\",\".ico\",\".tif\",\".tiff\"]\nDEFAULT_DIR = \"generated\"\n\n# we use the 0613 version of the models because we rely on the function calling API\nDEFAULT_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\n#  DEFAULT_MODEL = \"gpt-3.5-turbo-0613\" # gpt3.5 is going to be worse at generating code but faster. \nDEFAULT_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."
  },
  {
    "path": "v0/debugger.py",
    "content": "import modal\nimport os\nfrom constants import DEFAULT_DIR, DEFAULT_MODEL, DEFAULT_MAX_TOKENS, EXTENSION_TO_SKIP\n\nstub = modal.Stub(\"smol-debugger-v1\")\nopenai_image = modal.Image.debian_slim().pip_install(\"openai\")\n\n\n\ndef read_file(filename):\n    with open(filename, 'r') as file:\n        return file.read()\n\ndef walk_directory(directory):\n    code_contents = {}\n    for dirpath, _, filenames in os.walk(directory):\n        for filename in filenames:\n            if not any(filename.endswith(ext) for ext in EXTENSION_TO_SKIP):\n                try:\n                    relative_filepath = os.path.relpath(os.path.join(dirpath, filename), directory)\n                    code_contents[relative_filepath] = read_file(os.path.join(dirpath, filename))\n                except Exception as e:\n                    code_contents[relative_filepath] = f\"Error reading file {filename}: {str(e)}\"\n    return code_contents\n\n\n\n@stub.local_entrypoint()\ndef main(prompt, directory=DEFAULT_DIR, model=\"gpt-3.5-turbo\"):\n  code_contents = walk_directory(directory)\n\n  # Now, `code_contents` is a dictionary that contains the content of all your non-image files\n  # You can send this to OpenAI's text-davinci-003 for help\n\n  context = \"\\n\".join(f\"{path}:\\n{contents}\" for path, contents in code_contents.items())\n  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.\"\n  prompt = \"My files are as follows: \" + context + \"\\n\\n\" + \"My issue is as follows: \" + prompt\n  prompt += \"\\n\\nGive me ideas for what could be wrong and what fixes to do in which files.\"\n  res = generate_response.call(system, prompt, model)\n  # print res in teal\n  print(\"\\033[96m\" + res + \"\\033[0m\")\n\n\n@stub.function(\n    image=openai_image,\n    secret=modal.Secret.from_dotenv(),\n    retries=modal.Retries(\n        max_retries=3,\n        backoff_coefficient=2.0,\n        initial_delay=1.0,\n    ),\n    concurrency_limit=5,\n    timeout=120,\n)\ndef generate_response(system_prompt, user_prompt, model=\"gpt-3.5-turbo\", *args):\n    import openai\n\n    # Set up your OpenAI API credentials\n    openai.api_key = os.environ[\"OPENAI_API_KEY\"]\n\n    messages = []\n    messages.append({\"role\": \"system\", \"content\": system_prompt})\n    messages.append({\"role\": \"user\", \"content\": user_prompt})\n    # loop thru each arg and add it to messages alternating role between \"assistant\" and \"user\"\n    role = \"assistant\"\n    for value in args:\n        messages.append({\"role\": role, \"content\": value})\n        role = \"user\" if role == \"assistant\" else \"assistant\"\n\n    params = {\n        'model': model,\n        # \"model\": \"gpt-4\",\n        \"messages\": messages,\n        \"max_tokens\": 1500,\n        \"temperature\": 0,\n    }\n\n    # Send the API request\n    response = openai.ChatCompletion.create(**params)\n\n    # Get the reply from the API response\n    reply = response.choices[0][\"message\"][\"content\"]\n    return reply"
  },
  {
    "path": "v0/debugger_no_modal.py",
    "content": "import sys\nimport os\nfrom time import sleep\nfrom constants import DEFAULT_DIR, DEFAULT_MODEL, DEFAULT_MAX_TOKENS, EXTENSION_TO_SKIP\nimport argparse\ndef read_file(filename):\n    with open(filename, \"r\") as file:\n        return file.read()\n\n\ndef walk_directory(directory):\n    image_extensions = [\n        \".png\",\n        \".jpg\",\n        \".jpeg\",\n        \".gif\",\n        \".bmp\",\n        \".svg\",\n        \".ico\",\n        \".tif\",\n        \".tiff\",\n    ]\n    code_contents = {}\n    for root, dirs, files in os.walk(directory):\n        for file in files:\n            if not any(file.endswith(ext) for ext in image_extensions):\n                try:\n                    relative_filepath = os.path.relpath(\n                        os.path.join(root, file), directory\n                    )\n                    code_contents[relative_filepath] = read_file(\n                        os.path.join(root, file)\n                    )\n                except Exception as e:\n                    code_contents[\n                        relative_filepath\n                    ] = f\"Error reading file {file}: {str(e)}\"\n    return code_contents\n\n\ndef main(args):\n    prompt=args.prompt\n    directory= args.directory\n    model=args.model\n    code_contents = walk_directory(directory)\n\n    # Now, `code_contents` is a dictionary that contains the content of all your non-image files\n    # You can send this to OpenAI's text-davinci-003 for help\n\n    context = \"\\n\".join(\n        f\"{path}:\\n{contents}\" for path, contents in code_contents.items()\n    )\n    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.\"\n    prompt = (\n        \"My files are as follows: \"\n        + context\n        + \"\\n\\n\"\n        + \"My issue is as follows: \"\n        + prompt\n    )\n    prompt += (\n        \"\\n\\nGive me ideas for what could be wrong and what fixes to do in which files.\"\n    )\n    res = generate_response(system, prompt, model)\n    # print res in teal\n    print(\"\\033[96m\" + res + \"\\033[0m\")\n\n\ndef generate_response(system_prompt, user_prompt, model=DEFAULT_MODEL, *args):\n    import openai\n\n    # Set up your OpenAI API credentials\n    openai.api_key = os.environ[\"OPENAI_API_KEY\"]\n\n    messages = []\n    messages.append({\"role\": \"system\", \"content\": system_prompt})\n    messages.append({\"role\": \"user\", \"content\": user_prompt})\n    # loop thru each arg and add it to messages alternating role between \"assistant\" and \"user\"\n    role = \"assistant\"\n    for value in args:\n        messages.append({\"role\": role, \"content\": value})\n        role = \"user\" if role == \"assistant\" else \"assistant\"\n\n    params = {\n        \"model\": model,\n        # \"model\": \"gpt-4\",\n        \"messages\": messages,\n        \"max_tokens\": 1500,\n        \"temperature\": 0,\n    }\n\n    # Send the API request\n    keep_trying = True\n    while keep_trying:\n        try:\n            response = openai.ChatCompletion.create(**params)\n            keep_trying = False\n        except Exception as e:\n            # e.g. when the API is too busy, we don't want to fail everything\n            print(\"Failed to generate response. Error: \", e)\n            sleep(30)\n            print(\"Retrying...\")\n\n    # Get the reply from the API response\n    reply = response.choices[0][\"message\"][\"content\"]\n    return reply\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\n        \"prompt\",\n        help=\"The prompt to use for the AI. This should be the error message or issue you are facing.\",\n        \n    )\n    parser.add_argument(\n        \"--directory\",\n        \"-d\",\n        help=\"The directory to use for the AI. This should be the directory containing the files you want to debug.\",\n        default=DEFAULT_DIR,\n    )\n    parser.add_argument(\n        \"--model\",\n        \"-m\",\n        help=\"The model to use for the AI. This should be the model ID of the model you want to use.\",\n        default=DEFAULT_MODEL,\n    )\n    args = parser.parse_args()\n    main(args)\n"
  },
  {
    "path": "v0/exampleChromeExtension/background.js",
    "content": "chrome.runtime.onInstalled.addListener(() => {\n  chrome.action.onClicked.addListener((tab) => {\n    chrome.scripting.executeScript({\n      target: { tabId: tab.id },\n      files: ['content_script.js'],\n    });\n  });\n});\n\nchrome.runtime.onMessage.addListener((request, sender, sendResponse) => {\n  if (request.action === 'storePageContent') {\n    chrome.storage.local.set({ pageContent: request }, () => {\n      sendResponse({ success: true });\n    });\n  } else if (request.action === 'getPageContent') {\n    chrome.storage.local.get(['pageContent'], (result) => {\n      sendResponse(result);\n    });\n  }\n  return true;\n});"
  },
  {
    "path": "v0/exampleChromeExtension/content_script.js",
    "content": "function extractPageContent() {\n  const pageTitle = document.title;\n  const pageContent = document.body.innerText;\n  return { pageTitle, pageContent };\n}\n\nfunction sendMessageToBackground(action, data) {\n  chrome.runtime.sendMessage({ action, ...data }, (response) => {\n    if (response.success) {\n      console.log('Page content sent to background.');\n    } else {\n      console.error('Failed to send page content to background.');\n    }\n  });\n}\n\nconst pageData = extractPageContent();\nsendMessageToBackground('storePageContent', pageData);"
  },
  {
    "path": "v0/exampleChromeExtension/manifest.json",
    "content": "{\n  \"manifest_version\": 3,\n  \"name\": \"Anthropic Claude Summary Extension\",\n  \"version\": \"1.0\",\n  \"description\": \"A Chrome extension that summarizes web pages using the Anthropic Claude API.\",\n  \"permissions\": [\n    \"activeTab\",\n    \"storage\"\n  ],\n  \"action\": {\n    \"default_popup\": \"popup.html\",\n    \"default_icon\": {\n      \"16\": \"icon16.png\",\n      \"48\": \"icon48.png\",\n      \"128\": \"icon128.png\"\n    }\n  },\n  \"background\": {\n    \"service_worker\": \"background.js\"\n  },\n  \"content_scripts\": [\n    {\n      \"matches\": [\"<all_urls>\"],\n      \"js\": [\"content_script.js\"]\n    }\n  ],\n  \"icons\": {\n    \"16\": \"icon16.png\",\n    \"48\": \"icon48.png\",\n    \"128\": \"icon128.png\"\n  }\n}"
  },
  {
    "path": "v0/exampleChromeExtension/popup.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Anthropic Summary</title>\n  <link rel=\"stylesheet\" href=\"styles.css\">\n</head>\n<body>\n  <div id=\"content\"></div>\n  <div id=\"loadingIndicator\"></div>\n  <textarea id=\"userPrompt\"></textarea>\n  <button id=\"sendButton\">Submit</button>\n  <script src=\"popup.js\"></script>\n</body>\n</html>"
  },
  {
    "path": "v0/exampleChromeExtension/popup.js",
    "content": "document.addEventListener('DOMContentLoaded', () => {\n  const content = document.getElementById('content');\n  const userPrompt = document.getElementById('userPrompt');\n  const sendButton = document.getElementById('sendButton');\n  const loadingIndicator = document.getElementById('loadingIndicator');\n\n  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:\n\n  <h1>{title here}</h1>\n  <h2>{section title here}</h2>\n  <ul>\n    <li><strong>{first point}</strong>: {short explanation with details, and important links}</li>\n    <li><strong>{second point}</strong>: {short explanation with details, and important links}</li>\n    <!-- etc -->\n  </ul>\n  <h2>{second section here}</h2>\n  <ul>\n    <!-- etc -->\n  </ul>\n  <!-- etc -->\n\n  With all the words in brackets replaced by the summary of the content.\n\n  Assistant:`;\n\n  function requestAnthropicSummary(prompt, apiKey) {\n    chrome.runtime.sendMessage({ action: 'getPageContent' }, (response) => {\n      const { pageTitle, pageContent } = response.pageContent;\n      const fullPrompt = `${pageTitle}\\n\\n${pageContent}\\n\\n${prompt}`;\n\n      loadingIndicator.innerHTML = `Read page content of ${pageTitle}`;\n      loadingIndicator.style.display = 'block';\n      content.innerHTML = '';\n      userPrompt.disabled = true;\n      sendButton.disabled = true;\n\n      fetch('https://api.anthropic.com/v1/complete', {\n        method: 'POST',\n        headers: {\n          'x-api-key': apiKey,\n          'content-type': 'application/json',\n        },\n        body: JSON.stringify({\n          prompt: fullPrompt,\n          model: 'claude-instant-v1-100k',\n          max_tokens_to_sample: 1000,\n          stop_sequences: ['\\n\\nHuman:'],\n        }),\n      })\n        .then((res) => {\n          if (res.status === 401) {\n            chrome.storage.local.remove(['apiKey']);\n            throw new Error('Invalid API key');\n          }\n          return res.json();\n        })\n        .then((data) => {\n          content.innerHTML = data.completion;\n          loadingIndicator.style.display = 'none';\n          userPrompt.disabled = false;\n          sendButton.disabled = false;\n        })\n        .catch((error) => {\n          console.error(error);\n          loadingIndicator.style.display = 'none';\n          userPrompt.disabled = false;\n          sendButton.disabled = false;\n        });\n    });\n  }\n\n  chrome.storage.local.get(['apiKey'], (result) => {\n    if (!result.apiKey) {\n      const apiKey = prompt('Please enter your Anthropic Claude API key:');\n      chrome.storage.local.set({ apiKey }, () => {\n        requestAnthropicSummary(defaultPrompt, apiKey);\n      });\n    } else {\n      requestAnthropicSummary(defaultPrompt, result.apiKey);\n    }\n  });\n\n  sendButton.addEventListener('click', () => {\n    chrome.storage.local.get(['apiKey'], (result) => {\n      requestAnthropicSummary(userPrompt.value, result.apiKey);\n    });\n  });\n});"
  },
  {
    "path": "v0/exampleChromeExtension/prompt used for this extension.md",
    "content": "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.\n\n\n- When clicked:\n  - it injects a content script `content_script.js` on the currently open tab, \n  and accesses the title `pageTitle` and main content `pageContent` of the currently open page \n  (extracted via an injected content script, and sent over using a `storePageContent` action) \n  - in the background, receives the `storePageContent` data and stores it\n  - pops up a small window with a simple, modern, slick, minimalistic styled html popup\n  - in the popup script\n    - retrieves the page content data using a `getPageContent` action (and the background listens for the `getPageContent` action and retrieves that data) \n    - check extension storage for an `apiKey`, and if it isn't stored, asks for an API key to Anthropic Claude and stores it.\n    - calls the Anthropic model endpoint https://api.anthropic.com/v1/complete with the `claude-instant-v1-100k` model with: \n      - append the page title\n      - append the page content\n      - 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.\n        in this format:\n        ```js\n        defaultPrompt = `Human: Please provide a detailed, easy to read HTML summary of the given content \n        with 3-4 highlights per section with important keywords bolded and important links preserved, in this format:\n        \n        <h1>{title here}</h1>\n        <h2>{section title here}</h2>\n        <ul>\n          <!-- if there is an important, relevant image --> <img src={main image, if any} style=\"height:8rem\">\n          <li><strong>{first point}</strong>: {short explanation with details, with any relevant links included}</li>\n          <li><strong>{second point}</strong>: {short explanation with details, with any relevant links included}</li>\n          <li><strong>{third point}</strong>: <!-- etc -->\n          <!-- etc -->\n        </ul>\n        <h2>{second section here}</h2>\n        <ul>\n          <!-- etc -->\n        </ul>\n        <!-- etc -->\n\n        With all the words in brackets replaced by the summary of the content. Only draw from the source content, do not hallucinate.\n\n        Assistant:`;\n        ```js\n    - renders the Anthropic-generated HTML summary inside of the popup in a div with an id of content\n  - 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`.\n    - when `sendButton` is clicked, lets the user re-ask Anthropic with the same page title and page content but different prompt (from `userPrompt`).\n    - disable these inputs while it waits for the Anthropic api call to complete\n\nImportant Details:\n\n- It has to run in a browser environment, so no Nodejs APIs allowed.\n\n- 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.\n\n- the return signature of the anthropic api is curl https://api.anthropic.com/v1/complete\\\n  -H \"x-api-key: $API_KEY\"\\\n  -H 'content-type: application/json'\\\n  -d '{\n    \"prompt\": \"\\n\\nHuman: Tell me a haiku about trees\\n\\nAssistant: \",\n    \"model\": \"claude-v1\", \"max_tokens_to_sample\": 1000, \"stop_sequences\": [\"\\n\\nHuman:\"]\n  }'\n{\"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}\n\n- 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.\n\n- if the Anthropic api call is a 401, handle that by clearing the stored anthropic api key and asking for it again.\n\n- 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.\n\n- style the popup body with a minimum width of 400 and height of 600.\n\n## debugging notes\n\ninside of background.js, just take the getPageContent response directly\n\n```js\nchrome.runtime.onMessage.addListener((request, sender, sendResponse) => {\n  if (request.action === 'storePageContent') {\n    // dont access request.pageContent\n    chrome.storage.local.set({ pageContent: request }, () => {\n      sendResponse({ success: true });\n    });\n  } else if (request.action === 'getPageContent') {\n    chrome.storage.local.get(['pageContent'], (result) => {\n      // dont access request.pageContent\n      sendResponse(result);\n    });\n  }\n  return true;\n});\n```\n\ninside of popup.js, Update the function calls to `requestAnthropicSummary`\nin `popup.js` to pass the `apiKey`:\n\n```javascript\nchrome.storage.local.get(['apiKey'], (result) => {\n  const apiKey = result.apiKey;\n  requestAnthropicSummary(defaultPrompt, apiKey);\n});\n\nsendButton.addEventListener('click', () => {\n  chrome.storage.local.get(['apiKey'], (result) => {\n    const apiKey = result.apiKey;\n    requestAnthropicSummary(userPrompt.value, apiKey);\n  });\n});\n```\n\nin `popup.js`, store the defaultPrompt at the top level.\nalso, give a HTML format to the anthropic prompt\n"
  },
  {
    "path": "v0/exampleChromeExtension/shared_dependencies.md",
    "content": "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.\n\nthe files we have decided to generate are: content_script.js, background.js, popup.html, popup.js, popup.css\n\nShared dependencies:\n\n1. Exported variables:\n   - pageTitle\n   - pageContent\n\n2. Data schemas:\n   - storePageContent action data: { action: 'storePageContent', pageTitle, pageContent }\n   - getPageContent action data: { action: 'getPageContent' }\n\n3. ID names of DOM elements:\n   - content\n   - userPrompt\n   - sendButton\n   - loadingIndicator\n\n4. Message names:\n   - storePageContent\n   - getPageContent\n\n5. Function names:\n   - requestAnthropicSummary\n\n6. API endpoints:\n   - https://api.anthropic.com/v1/complete\n\n7. Model name:\n   - claude-instant-v1-100k\n\n8. Default prompt:\n   - defaultPrompt"
  },
  {
    "path": "v0/exampleChromeExtension/styles.css",
    "content": "body {\n  margin: 16px;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n  min-width: 400px;\n  min-height: 600px;\n}\n\n#content {\n  margin-bottom: 16px;\n}\n\n#userPrompt {\n  width: 100%;\n  height: 80px;\n  resize: none;\n  margin-bottom: 8px;\n}\n\n#sendButton {\n  display: inline-block;\n  background-color: #007bff;\n  color: white;\n  padding: 8px 16px;\n  border-radius: 4px;\n  text-decoration: none;\n  cursor: pointer;\n  transition: background-color 0.2s;\n}\n\n#sendButton:hover {\n  background-color: #0056b3;\n}\n\n#sendButton:disabled {\n  background-color: #6c757d;\n  cursor: not-allowed;\n}\n\n#loadingIndicator {\n  display: none;\n  width: 100%;\n  height: 8rem;\n  border-radius: 1rem;\n  margin-bottom: 2rem;\n  background-image: linear-gradient(-45deg, #007bff 25%, transparent 25%, transparent 50%, #007bff 50%, #007bff 75%, transparent 75%, transparent);\n  background-size: 40px 40px;\n  animation: loading 1s linear infinite;\n}\n\n@keyframes loading {\n  0% {\n    background-position: 40px 0;\n  }\n  100% {\n    background-position: 0 0;\n  }\n}"
  },
  {
    "path": "v0/main.py",
    "content": "import os\nimport modal\nimport ast\nfrom utils import clean_dir\nfrom constants import DEFAULT_DIR, DEFAULT_MODEL, DEFAULT_MAX_TOKENS\n\nstub = modal.Stub(\"smol-developer-v1\") # yes we are recommending using Modal by default, as it helps with deployment. see readme for why.\nopenai_image = modal.Image.debian_slim().pip_install(\"openai\", \"tiktoken\")\n\n@stub.function(\n    image=openai_image,\n    secret=modal.Secret.from_dotenv(),\n    retries=modal.Retries(\n        max_retries=5,\n        backoff_coefficient=2.0,\n        initial_delay=1.0,\n    ),\n    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\n    timeout=120,\n)\ndef generate_response(model, system_prompt, user_prompt, *args):\n    # IMPORTANT: Keep import statements here due to Modal container restrictions https://modal.com/docs/guide/custom-container#additional-python-packages\n    import openai\n    import tiktoken\n\n    def reportTokens(prompt):\n        encoding = tiktoken.encoding_for_model(model)\n        # print number of tokens in light gray, with first 50 characters of prompt in green. if truncated, show that it is truncated\n        print(\"\\033[37m\" + str(len(encoding.encode(prompt))) + \" tokens\\033[0m\" + \" in prompt: \" + \"\\033[92m\" + prompt[:50] + \"\\033[0m\" + (\"...\" if len(prompt) > 50 else \"\"))\n        \n\n    # Set up your OpenAI API credentials\n    openai.api_key = os.environ[\"OPENAI_API_KEY\"]\n\n    messages = []\n    messages.append({\"role\": \"system\", \"content\": system_prompt})\n    reportTokens(system_prompt)\n    messages.append({\"role\": \"user\", \"content\": user_prompt})\n    reportTokens(user_prompt)\n    # Loop through each value in `args` and add it to messages alternating role between \"assistant\" and \"user\"\n    role = \"assistant\"\n    for value in args:\n        messages.append({\"role\": role, \"content\": value})\n        reportTokens(value)\n        role = \"user\" if role == \"assistant\" else \"assistant\"\n\n    params = {\n        \"model\": model,\n        \"messages\": messages,\n        \"max_tokens\": DEFAULT_MAX_TOKENS,\n        \"temperature\": 0,\n    }\n\n    # Send the API request\n    response = openai.ChatCompletion.create(**params)\n\n    # Get the reply from the API response\n    reply = response.choices[0][\"message\"][\"content\"]\n    return reply\n\n\n@stub.function()\ndef generate_file(filename, model=DEFAULT_MODEL, filepaths_string=None, shared_dependencies=None, prompt=None):\n    # call openai api with this prompt\n    filecode = generate_response.call(model, \n        f\"\"\"You are an AI developer who is trying to write a program that will generate code for the user based on their intent.\n        \n    the app is: {prompt}\n\n    the files we have decided to generate are: {filepaths_string}\n\n    the shared dependencies (like filenames and variable names) we have decided on are: {shared_dependencies}\n    \n    only write valid code for the given filepath and file type, and return only the code.\n    do not add any other explanation, only return valid code for that file type.\n    \"\"\",\n        f\"\"\"\n    We have broken up the program into per-file generation. \n    Now your job is to generate only the code for the file {filename}. \n    Make sure to have consistent filenames if you reference other files we are also generating.\n    \n    Remember that you must obey 3 things: \n       - you are generating code for the file {filename}\n       - do not stray from the names of the files and the shared dependencies we have decided on\n       - 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\n    \n    Bad response:\n    ```javascript \n    console.log(\"hello world\")\n    ```\n    \n    Good response:\n    console.log(\"hello world\")\n    \n    Begin generating the code now.\n\n    \"\"\",\n    )\n\n    return filename, filecode\n\n\n@stub.local_entrypoint()\ndef main(prompt, directory=DEFAULT_DIR, model=DEFAULT_MODEL, file=None):\n    # read file from prompt if it ends in a .md filetype\n    if prompt.endswith(\".md\"):\n        with open(prompt, \"r\") as promptfile:\n            prompt = promptfile.read()\n\n    print(\"hi its me, 🐣the smol developer🐣! you said you wanted:\")\n    # print the prompt in green color\n    print(\"\\033[92m\" + prompt + \"\\033[0m\")\n\n    # call openai api with this prompt\n    filepaths_string = generate_response.call(model, \n        \"\"\"You are an AI developer who is trying to write a program that will generate code for the user based on their intent.\n        \n    When given their intent, create a complete, exhaustive list of filepaths that the user would write to make the program.\n    \n    only list the filepaths you would write, and return them as a python list of strings. \n    do not add any other explanation, only return a python list of strings.\n\n    Example output:\n    [\"index.html\", \"style.css\", \"script.js\"]\n    \"\"\",\n        prompt,\n    )\n    print(filepaths_string)\n    # parse the result into a python list\n    list_actual = []\n    try:\n        list_actual = ast.literal_eval(filepaths_string)\n\n        # if shared_dependencies.md is there, read it in, else set it to None\n        shared_dependencies = None\n        if os.path.exists(\"shared_dependencies.md\"):\n            with open(\"shared_dependencies.md\", \"r\") as shared_dependencies_file:\n                shared_dependencies = shared_dependencies_file.read()\n\n        if file is not None:\n            # check file\n            print(\"file\", file)\n            filename, filecode = generate_file(file, model=model, filepaths_string=filepaths_string, shared_dependencies=shared_dependencies, prompt=prompt)\n            write_file(filename, filecode, directory)\n        else:\n            clean_dir(directory)\n\n            # understand shared dependencies\n            shared_dependencies = generate_response.call(model, \n                \"\"\"You are an AI developer who is trying to write a program that will generate code for the user based on their intent.\n                \n            In response to the user's prompt:\n\n            ---\n            the app is: {prompt}\n            ---\n            \n            the files we have decided to generate are: {filepaths_string}\n\n            Now that we have a list of files, we need to understand what dependencies they share.\n            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.\n            Exclusively focus on the names of the shared dependencies, and do not add any other explanation.\n            \"\"\",\n                prompt,\n            )\n            print(shared_dependencies)\n            # write shared dependencies as a md file inside the generated directory\n            write_file(\"shared_dependencies.md\", shared_dependencies, directory)\n            \n            # Iterate over generated files and write them to the specified directory\n            for filename, filecode in generate_file.map(\n                list_actual, order_outputs=False, kwargs=dict(model=model, filepaths_string=filepaths_string, shared_dependencies=shared_dependencies, prompt=prompt)\n            ):\n                write_file(filename, filecode, directory)\n\n\n    except ValueError:\n        print(\"Failed to parse result\")\n\n\ndef write_file(filename, filecode, directory):\n    # Output the filename in blue color\n    print(\"\\033[94m\" + filename + \"\\033[0m\")\n    print(filecode)\n    \n    file_path = os.path.join(directory, filename)\n    dir = os.path.dirname(file_path)\n\n    # Check if the filename is actually a directory\n    if os.path.isdir(file_path):\n        print(f\"Error: {filename} is a directory, not a file.\")\n        return\n\n    os.makedirs(dir, exist_ok=True)\n\n    # Open the file in write mode\n    with open(file_path, \"w\") as file:\n        # Write content to the file\n        file.write(filecode)\n\n"
  },
  {
    "path": "v0/main_no_modal.py",
    "content": "import sys\nimport os\nimport ast\nfrom time import sleep\nfrom utils import clean_dir\nfrom constants import DEFAULT_DIR, DEFAULT_MODEL, DEFAULT_MAX_TOKENS\n\n\ndef generate_response(system_prompt, user_prompt, *args):\n    import openai\n    import tiktoken\n\n    def reportTokens(prompt):\n        encoding = tiktoken.encoding_for_model(DEFAULT_MODEL)\n        # print number of tokens in light gray, with first 10 characters of prompt in green\n        print(\n            \"\\033[37m\"\n            + str(len(encoding.encode(prompt)))\n            + \" tokens\\033[0m\"\n            + \" in prompt: \"\n            + \"\\033[92m\"\n            + prompt[:50]\n            + \"\\033[0m\"\n        )\n\n    # Set up your OpenAI API credentials\n    openai.api_key = os.environ[\"OPENAI_API_KEY\"]\n\n    messages = []\n    messages.append({\"role\": \"system\", \"content\": system_prompt})\n    reportTokens(system_prompt)\n    messages.append({\"role\": \"user\", \"content\": user_prompt})\n    reportTokens(user_prompt)\n    # loop thru each arg and add it to messages alternating role between \"assistant\" and \"user\"\n    role = \"assistant\"\n    for value in args:\n        messages.append({\"role\": role, \"content\": value})\n        reportTokens(value)\n        role = \"user\" if role == \"assistant\" else \"assistant\"\n\n    params = {\n        \"model\": DEFAULT_MODEL,\n        \"messages\": messages,\n        \"max_tokens\": DEFAULT_MAX_TOKENS,\n        \"temperature\": 0,\n    }\n\n    # Send the API request\n    keep_trying = True\n    while keep_trying:\n        try:\n            response = openai.ChatCompletion.create(**params)\n            keep_trying = False\n        except Exception as e:\n            # e.g. when the API is too busy, we don't want to fail everything\n            print(\"Failed to generate response. Error: \", e)\n            sleep(30)\n            print(\"Retrying...\")\n\n    # Get the reply from the API response\n    reply = response.choices[0][\"message\"][\"content\"]\n    return reply\n\n\ndef generate_file(\n    filename, filepaths_string=None, shared_dependencies=None, prompt=None\n):\n    # call openai api with this prompt\n    filecode = generate_response(\n        f\"\"\"You are an AI developer who is trying to write a program that will generate code for the user based on their intent.\n\n    the app is: {prompt}\n\n    the files we have decided to generate are: {filepaths_string}\n\n    the shared dependencies (like filenames and variable names) we have decided on are: {shared_dependencies}\n\n    only write valid code for the given filepath and file type, and return only the code.\n    do not add any other explanation, only return valid code for that file type.\n    \"\"\",\n        f\"\"\"\n    We have broken up the program into per-file generation.\n    Now your job is to generate only the code for the file {filename}.\n    Make sure to have consistent filenames if you reference other files we are also generating.\n\n    Remember that you must obey 3 things:\n       - you are generating code for the file {filename}\n       - do not stray from the names of the files and the shared dependencies we have decided on\n       - 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\n\n    Bad response:\n    ```javascript\n    console.log(\"hello world\")\n    ```\n\n    Good response:\n    console.log(\"hello world\")\n\n    Begin generating the code now.\n\n    \"\"\",\n    )\n\n    return filename, filecode\n\n\ndef main(prompt, directory=DEFAULT_DIR, file=None):\n    # read file from prompt if it ends in a .md filetype\n    if prompt.endswith(\".md\"):\n        with open(prompt, \"r\") as promptfile:\n            prompt = promptfile.read()\n\n    print(\"hi its me, 🐣the smol developer🐣! you said you wanted:\")\n    # print the prompt in green color\n    print(\"\\033[92m\" + prompt + \"\\033[0m\")\n\n    # example prompt:\n    # a Chrome extension that, when clicked, opens a small window with a page where you can enter\n    # a prompt for reading the currently open page and generating some response from openai\n\n    # call openai api with this prompt\n    filepaths_string = generate_response(\n        \"\"\"You are an AI developer who is trying to write a program that will generate code for the user based on their intent.\n\n    When given their intent, create a complete, exhaustive list of filepaths that the user would write to make the program.\n\n    only list the filepaths you would write, and return them as a python list of strings.\n    do not add any other explanation, only return a python list of strings.\n    \"\"\",\n        prompt,\n    )\n    print(filepaths_string)\n    # parse the result into a python list\n    list_actual = []\n    try:\n        list_actual = ast.literal_eval(filepaths_string)\n\n        # if shared_dependencies.md is there, read it in, else set it to None\n        shared_dependencies = None\n        if os.path.exists(\"shared_dependencies.md\"):\n            with open(\"shared_dependencies.md\", \"r\") as shared_dependencies_file:\n                shared_dependencies = shared_dependencies_file.read()\n\n        if file is not None:\n            # check file\n            print(\"file\", file)\n            filename, filecode = generate_file(\n                file,\n                filepaths_string=filepaths_string,\n                shared_dependencies=shared_dependencies,\n                prompt=prompt,\n            )\n            write_file(filename, filecode, directory)\n        else:\n            clean_dir(directory)\n\n            # understand shared dependencies\n            shared_dependencies = generate_response(\n                \"\"\"You are an AI developer who is trying to write a program that will generate code for the user based on their intent.\n\n            In response to the user's prompt:\n\n            ---\n            the app is: {prompt}\n            ---\n\n            the files we have decided to generate are: {filepaths_string}\n\n            Now that we have a list of files, we need to understand what dependencies they share.\n            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.\n            Exclusively focus on the names of the shared dependencies, and do not add any other explanation.\n            \"\"\",\n                prompt,\n            )\n            print(shared_dependencies)\n            # write shared dependencies as a md file inside the generated directory\n            write_file(\"shared_dependencies.md\", shared_dependencies, directory)\n\n            for name in list_actual:\n                filename, filecode = generate_file(\n                    name,\n                    filepaths_string=filepaths_string,\n                    shared_dependencies=shared_dependencies,\n                    prompt=prompt,\n                )\n                write_file(filename, filecode, directory)\n\n    except ValueError:\n        print(\"Failed to parse result: \" + result)\n\n\ndef write_file(filename, filecode, directory):\n    # Output the filename in blue color\n    print(\"\\033[94m\" + filename + \"\\033[0m\")\n    print(filecode)\n\n    file_path = directory + \"/\" + filename\n    dir = os.path.dirname(file_path)\n    os.makedirs(dir, exist_ok=True)\n\n    # if file_path does not end with \"/\"\n    if not file_path.endswith(\"/\"):\n        with open(file_path, \"w\") as file:\n            # Write content to the file\n            file.write(filecode)\n\n\nif __name__ == \"__main__\":\n\n    # Check for arguments\n    if len(sys.argv) < 2:\n\n        # Looks like we don't have a prompt. Check if prompt.md exists\n        if not os.path.exists(\"prompt.md\"):\n\n            # Still no? Then we can't continue\n            print(\"Please provide a prompt\")\n            sys.exit(1)\n\n        # Still here? Assign the prompt file name to prompt\n        prompt = \"prompt.md\"\n\n    else:\n        # Set prompt to the first argument\n        prompt = sys.argv[1]\n\n    # Pull everything else as normal\n    directory = sys.argv[2] if len(sys.argv) > 2 else DEFAULT_DIR\n    file = sys.argv[3] if len(sys.argv) > 3 else None\n\n    # Run the main function\n    main(prompt, directory, file)\n"
  },
  {
    "path": "v0/readme.md",
    "content": "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.\n\nit has been rewritten from scratch in the root folder, but is preserved here for others to easily reference."
  },
  {
    "path": "v0/static/readme.md",
    "content": "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."
  },
  {
    "path": "v0/utils.py",
    "content": "import os\nfrom constants import EXTENSION_TO_SKIP\n\ndef clean_dir(directory):\n    # Check if the directory exists\n    if os.path.exists(directory):\n        # If it does, iterate over all files and directories\n        for dirpath, _, filenames in os.walk(directory):\n            for filename in filenames:\n                _, extension = os.path.splitext(filename)\n                if extension not in EXTENSION_TO_SKIP:\n                    os.remove(os.path.join(dirpath, filename))\n    else:\n        os.makedirs(directory, exist_ok=True)"
  },
  {
    "path": "v0/v0_readme.md",
    "content": "# smol developer\n\n<a href=\"https://app.e2b.dev/agent/smol-developer\" target=\"_blank\" rel=\"noopener noreferrer\">\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://app.e2b.dev/api/badge_light\">\n  <img alt=\"Deploy agent on e2b button\" src=\"https://app.e2b.dev/api/badge\"/>\n</picture>\n</a>\n\n***Human-centric & Coherent Whole Program Synthesis*** aka your own personal junior developer\n\n> [Build the thing that builds the thing!](https://twitter.com/swyx/status/1657578738345979905) a `smol dev` for every dev in every situation\n\nthis 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.\n\nAI 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.\n\n*Not no code, not low code, but some third thing.* \n\nPerhaps 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.\n\n## examples/prompt gallery\n\n- [6 minute video demo](https://youtu.be/UCo7YeTy-aE) - (sorry for sped up audio, we were optimizing for twitter, bad call)\n  - 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\n  - 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)\n- `smol-plugin` - prompt to ChatGPT plugin ([tweet](https://twitter.com/ultrasoundchad/status/1659366507409985536?s=20), [fork](https://github.com/gmchad/smol-plugin))\n\n  <img src=\"https://github.com/smol-ai/developer/assets/6764957/6ffaac3b-5d90-460a-a590-c8a8c004bd36\" height=200 />\n\n- [Prompt to Pokemon App](https://twitter.com/RobertCaracaus/status/1659312419485761536?s=20)\n  \n  <img src=\"https://github.com/smol-ai/developer/assets/6764957/15fa189a-3f52-4618-ac8e-2a77b6500264\" height=200 />\n  \n- [Political Campaign CRM Program example](https://github.com/smol-ai/developer/pull/22/files)\n- [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))\n- [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\n\n  <img src=\"https://github.com/smol-ai/developer/assets/6764957/e80058f1-ea9c-42dd-87ff-004b61f08f2e\" height=200 />\n  \n- [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\n\n  <img src=\"https://github.com/smol-ai/developer/assets/6764957/c51f9f8c-021d-446a-b44d-7a6f48e64550\" height=200 />\n\nI'm actively seeking more examples, please PR yours! \n\nsorry for the lack of examples, I know that is frustrating but I wasnt ready for so many of you lol\n\n## major forks/alternatives\n\nplease send in alternative implementations, and deploy strategies on alternative stacks!\n\n- **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)\n- **C#/Dotnet**: https://github.com/colhountech/smol-ai-dotnet in C#!\n- **Golang**: https://github.com/tmc/smol-dev-go in Go\n- https://github.com/gmchad/smol-plugin automatically generate @openai plugins by specifying your API in markdown in smol-developer style\n- your fork here!\n\n## arch diagram\n\nnaturally generated with gpt4, like [we did for babyagi](https://twitter.com/swyx/status/1648724820316786688)\n![image](https://github.com/smol-ai/developer/assets/6764957/f8fc68f4-77f6-43ee-852f-a35fb195430a)\n\n\n### innovations and insights\n\n> Please subscribe to https://latent.space/ for a fuller writeup and insights and reflections\n\n- **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)\n  - turns out you can specify prompts in code in prompts and gpt4 obeys that to the letter\n- **Copy and paste programming**\n  - 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\n  - 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\".\n- **Debugging by `cat`ing** the whole codebase with your error message and getting specific fix suggestions - particularly delightful!\n- **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. \n  - 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...\n  - ... 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. \n  - see `prompt.md` for SOTA smol-dev prompting\n- **Low activation energy for unfamiliar APIs**\n  - 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. \n  - 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\n  - 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.\n- **Modal is all you need** - we chose Modal to solve 4 things:\n  - solve python dependency hell in dev and prod\n  - parallelizable code generation\n  - simple upgrade path from local dev to cloud hosted endpoints (in future)\n  - fault tolerant openai api calls with retries/backoff, and attached storage (for future use)\n\n> Please subscribe to https://latent.space/ for a fuller writeup and insights and reflections\n\n### caveats\n\nWe 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.\n\nWe dont have access to GPT4-32k, but if we did, we'd explore dumping entire API/SDK documentation into context.\n\nThe 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).\n\n## install\n\nit's basically: \n\n- `git clone https://github.com/smol-ai/developer`.\n- copy over `.example.env` to `.env` filling in your API keys.\n\nThere are no python dependencies to wrangle thanks to using Modal as a [self-provisioning runtime](https://www.google.com/search?q=self+provisioning+runtime).\n\nUnfortunately this project also uses 3 other things:\n\n- Modal.com - [sign up](https://modal.com/signup), then `pip install modal-client && modal token new`\n  - You can run this project w/o Modal following these instructions:\n  - `pip install -r requirements.txt`\n  - `export OPENAI_API_KEY=sk-xxxxxx` (your openai api key here)\n  - `python main_no_modal.py YOUR_PROMPT_HERE`\n- 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.\n- (for the demo project only) anthropic claude 100k context api (private beta) - not important unless you're exactly trying to repro my demo\n\nyou'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.\n\n### trying the example chrome extension from the demo video\n\nthe `/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.`\n\n- go to Manage Extensions in Chrome\n- load unpacked\n- find the relevant folder in your file system and load it\n- go to any content heavy site\n- click the cute bird\n- see it work and rejoice\n\nthis 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.\n\n## usage: smol dev\n\nbasic usage (by default it runs with `gpt-3.5-turbo`, but we strongly encourage running with `gpt-4` if you have access)\n\n```bash\n# inline prompt\nmodal 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\n```\n\nafter 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\n\n```bash\n# prompt in markdown file\nmodal run main.py --prompt prompt.md --model=gpt-4\n```\n\neach time you run this, the generated directory is deleted (except for images) and all files are rewritten from scratch. \n\nIn 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)\n\n### smol dev in single file mode\n\nif 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:\n\n```bash\nmodal run main.py --prompt prompt.md  --file popup.js\n```\n\n### smol dev without modal.com\n\nBy 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).\n\nHowever if you want to just run it on your own machine, you can run smol dev w/o Modal following these instructions:\n\n```bash\npip install -r requirements.txt\nexport OPENAI_API_KEY=sk-xxxxxx # your openai api key here)\n\npython main_no_modal.py YOUR_PROMPT_HERE\n```\n\nIf 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)* \n\n## usage: smol debugger\n\n*this is a beta feature, very very MVP, just a proof of concept really*\n\ntake 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.\n\n```bash\nmodal 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)\"\n\n# gpt4\nmodal run debugger.py --prompt \"your_error msg_here\" --model=gpt-4\n```\n\n## usage: smol pm\n\n*this is even worse than beta, its kind of a \"let's see what happens\" experiment*\n\ntake 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.\n\n```bash\nmodal run code2prompt.py # ~0.5 second with gpt 3.5\n\n# use gpt4\nmodal run code2prompt.py --model=gpt-4 # 2 mins, MUCH better results\n```\n\nWe 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.\n\nNaturally, we had to try `code2prompt2code`...\n\n```bash\n# add prompt... this needed a few iterations to get right\nmodal 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\n\n# must go deeper\nmodal run main.py --prompt code2prompt-gpt4.md --directory code2prompt2code\n```\n\nWe leave the social and technical impacts of multilayer generative deep-frying of codebases as an exercise to the reader.\n\n## Development using a Dev Container\n\n> 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.\n\nWe 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.\n\nIf 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.\n\nHere are the steps to use the devcontainer:\n\n1. Open this project in VS Code.\n2. 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.\n3. 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.\n4. Once the build is finished, the VS Code window will reload and you are now working inside the devcontainer.\n\n\n<details>\n<summary> Benefits of a Dev Container </summary>\n\n1. **Consistent Environment**: Every developer works within the same development setup, eliminating \"it works on my machine\" issues and easing the onboarding of new contributors.\n\n2. **Sandboxing**: Your development environment is isolated from your local machine, allowing you to work on multiple projects with differing dependencies without conflict.\n\n3. **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.\n\n4. **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.\n\n5. **Portability**: This setup can be utilized on any computer with Docker and the appropriate IDE installed. Simply clone the repository and start the container.\n</details>\n\n\n## future directions\n\nthings to try/would accept open issue discussions and PRs:\n\n- **specify .md files for each generated file**, with further prompts that could finetune the output in each of them\n  - so basically like `popup.html.md` and `content_script.js.md` and so on\n- **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\n  - 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\n- **ability to install its own dependencies**\n  - 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)?\n  - Modal has an interesting possibility: generate functions that speak modal which also solves the dependency thing https://twitter.com/akshat_b/status/1658146096902811657\n- **self-heal** by running the code itself and use errors as information for reprompting \n  - however its a bit hard to get errors from the chrome extension environment so we did not try this\n- **using anthropic as the coding layer**\n  - you can run `modal run anthropic.py --prompt prompt.md --outputdir=anthropic` to try it\n  - but it doesnt work because anthropic doesnt follow instructions to generate file code very well.\n- **make agents that autonomously run this code in a loop/watch the prompt file** and regenerate code each time, on a new git branch\n  - the code could be generated on 5 simultaneous git branches and checking their output would just involve switching git branches\n"
  }
]