├── .gitignore ├── README.md ├── __tests__ ├── executeCommands.test.ts ├── outputInstructionMessage.test.ts └── processYamlObjects.test.ts ├── copy-source.sh ├── jest.config.js ├── package-lock.json ├── package.json ├── prompts ├── shell-hands.txt └── ts-runner-protocol.txt ├── src ├── index.ts └── runner.ts ├── test ├── 1.yaml ├── 2.yaml ├── mkdir-cd.yaml ├── patch.yaml └── planets.yaml └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yaml-runner 2 | 3 | yaml-runner is a command-line tool that automates the execution of tasks based on instructions defined in YAML files. It's main purpose is to execute instructions from GPT-4, or other LLMs. 4 | 5 | ## Features 6 | 7 | - Execute commands in sequence 8 | - Create and modify files 9 | - Display messages to the user 10 | - Easy-to-use YAML configuration 11 | 12 | ## Prerequisites and Dependencies 13 | 14 | Ensure you have Node.js and npm installed on your system. 15 | 16 | This project uses the following npm packages: 17 | - js-yaml 18 | - yargs 19 | - inquirer 20 | - chalk 21 | - boxen 22 | 23 | ## Installation and Setup 24 | 25 | 1. Clone the repository: 26 | ``` 27 | git clone https://github.com/mbusigin/yaml-runner 28 | ``` 29 | 2. Install dependencies: 30 | ``` 31 | cd yaml-runner 32 | npm install 33 | ``` 34 | 35 | ## Usage 36 | 37 | Have GPT-4 plan out some changes to your code base. Next, jam this into your system prompt: 38 | 39 | 1. For each natural language instruction, identify the primary action and its related components. The primary action should be one of the following: executing commands, creating or modifying files, or displaying messages. 40 | 41 | 2. Convert each primary action and its components into a corresponding YAML object using the YAML specification provided earlier. 42 | 43 | 3. Here's a guideline for mapping natural language instructions to YAML objects: 44 | 45 | a. If the instruction is about executing commands: 46 | - Identify the list of commands to be executed. 47 | - Create a YAML object with `type: commands` and list the commands under the `commands` field. 48 | 49 | b. If the instruction is about creating or modifying a file: 50 | - Identify the file name, file contents, and whether it's a new file or an existing file. 51 | - Create a YAML object with `type: file`, and include the `filename`, `contents`, and `append` fields as needed. 52 | 53 | c. If the instruction is about displaying a message to the user: 54 | - Identify the message to be displayed. 55 | - Create a YAML object with `type: instruction` and include the `message` field with the identified message. 56 | 57 | 4. Arrange the YAML objects in the order of the natural language instructions. 58 | 59 | 5. Output the final YAML content, combining all the created YAML objects. 60 | 61 | For example, if the natural language instructions are: 62 | - Create a new directory called "project". 63 | - Navigate to the "project" directory. 64 | - Display a message saying "Directory created and navigated." 65 | 66 | You would generate the following YAML content: 67 | 68 | ```yaml 69 | - type: commands 70 | commands: 71 | - mkdir project 72 | - cd project 73 | - type: instruction 74 | message: Directory created and navigated. 75 | ``` 76 | 77 | If you need to update a file, you need to *COMPLETELY* replace its contents with type: file -> contents. Every file must include all of its contents. 78 | 79 | Then execute the output instructions: 80 | 81 | To run yaml-runner, execute the following command with the path to your YAML file: 82 | ``` 83 | node src/runner.js path/to/your/yaml/file.yaml 84 | ``` 85 | 86 | ## Examples 87 | 88 | See the `test` directory for example YAML files demonstrating various features of yaml-runner. 89 | 90 | ## Contributing 91 | 92 | Contributions are welcome! Please read the CONTRIBUTING.md file for guidelines on how to contribute to this project. 93 | -------------------------------------------------------------------------------- /__tests__/executeCommands.test.ts: -------------------------------------------------------------------------------- 1 | import { executeCommands } from '../src/runner'; 2 | import * as fs from 'fs'; 3 | 4 | test('should execute commands', () => { 5 | const commands = [ 6 | 'mkdir test-directory', 7 | 'touch test-directory/test-file.txt', 8 | ]; 9 | executeCommands(commands); 10 | 11 | expect(fs.existsSync('test-directory')).toBeTruthy(); 12 | expect(fs.existsSync('test-directory/test-file.txt')).toBeTruthy(); 13 | 14 | // Clean up 15 | fs.rmSync('test-directory/test-file.txt'); 16 | fs.rmdirSync('test-directory'); 17 | }); 18 | -------------------------------------------------------------------------------- /__tests__/outputInstructionMessage.test.ts: -------------------------------------------------------------------------------- 1 | import { processYamlObjects } from '../src/runner'; 2 | 3 | test('should output instruction message', () => { 4 | const yamlObjects = [ 5 | { 6 | type: 'instruction', 7 | message: 'This is a test instruction', 8 | }, 9 | ]; 10 | 11 | const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); 12 | 13 | processYamlObjects(yamlObjects); 14 | 15 | expect(consoleSpy).toHaveBeenCalledWith('This is a test instruction'); 16 | 17 | consoleSpy.mockRestore(); 18 | }); 19 | -------------------------------------------------------------------------------- /__tests__/processYamlObjects.test.ts: -------------------------------------------------------------------------------- 1 | import { processYamlObjects } from '../src/runner'; 2 | import * as fs from 'fs'; 3 | 4 | test('should process YAML objects and perform actions', () => { 5 | const yamlObjects = [ 6 | { 7 | type: 'file', 8 | filename: 'test-file.txt', 9 | contents: 'Hello, World!', 10 | }, 11 | { 12 | type: 'commands', 13 | commands: ['mkdir test-directory'], 14 | }, 15 | ]; 16 | processYamlObjects(yamlObjects); 17 | 18 | expect(fs.readFileSync('test-file.txt', 'utf-8')).toBe('Hello, World!'); 19 | expect(fs.existsSync('test-directory')).toBeTruthy(); 20 | 21 | // Clean up 22 | fs.unlinkSync('test-file.txt'); 23 | fs.rmdirSync('test-directory'); 24 | }); 25 | -------------------------------------------------------------------------------- /copy-source.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "The following is a code-base for Golem:" 4 | echo 5 | echo 6 | find tests/ src/ golems/ -name '*.*' -print0 | xargs -0 -I{} sh -c 'echo {}; echo "==================================" ; echo; echo ; cat {} ; echo' 7 | echo 8 | echo 9 | echo "Understand Golem's code base. And just tell me you acknowledge -- nothing else." 10 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testMatch: ['**/__tests__/**/*.test.ts'], 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-runner", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "start": "ts-node src/cli.ts", 7 | "test": "jest" 8 | }, 9 | "devDependencies": { 10 | "@types/diff": "^5.0.3", 11 | "@types/jest": "^27.5.2", 12 | "@types/js-yaml": "^4.0.5", 13 | "jest": "^27.5.1", 14 | "ts-jest": "^27.1.5", 15 | "typescript": "^4.5.2" 16 | }, 17 | "dependencies": { 18 | "boxen": "^7.0.2", 19 | "chalk": "^5.2.0", 20 | "diff": "^5.1.0", 21 | "inquirer": "^9.1.5", 22 | "js-yaml": "^4.1.0", 23 | "yargs": "^17.7.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /prompts/shell-hands.txt: -------------------------------------------------------------------------------- 1 | Return shell commands in your responses formatted as an array of YAML objects. Possible YAML objects are: 2 | 3 | ```yaml 4 | - type: instruction 5 | message: "Here's a multi-file Go CLI program that takes 2 numbers and adds them. First, create the necessary files and directories:" 6 | - type: commands 7 | commands: 8 | - mkdir adder 9 | - cd adder 10 | - touch main.go add.go 11 | - type: instruction 12 | message: "Now, add the following code to:" 13 | - type: file 14 | filename: main.go 15 | contents: | 16 | package main 17 | 18 | func main() {} 19 | - type: instruction 20 | message: "Add a comment saying hello:" 21 | - type: file 22 | filename: main.go 23 | append: '/* hello */' 24 | ``` 25 | 26 | Also: make sure to put all of your messages in double quotes! 27 | -------------------------------------------------------------------------------- /prompts/ts-runner-protocol.txt: -------------------------------------------------------------------------------- 1 | YAML Runner: 2 | ---------------------------------------- 3 | 1. For each natural language instruction, identify the primary action and its related components. The primary action should be one of the following: executing commands, creating or modifying files, applying patches, or displaying messages. 4 | 5 | 2. Convert each primary action and its components into a corresponding YAML object using the YAML specification provided earlier. 6 | 7 | 3. Here's a guideline for mapping natural language instructions to YAML objects: 8 | 9 | a. If the instruction is about executing commands: 10 | - Identify the list of commands to be executed. 11 | - Create a YAML object with `type: commands` and list the commands under the `commands` field. 12 | 13 | b. If the instruction is about creating or modifying a file: 14 | - Identify the file name, file contents, and whether it's a new file or an existing file. 15 | - Create a YAML object with `type: file`, and include the `filename`, `contents`, and `append` fields as needed. 16 | - To update a file, you need to *COMPLETELY* replace its contents with type: file -> contents. Every file must include a complete, unabridged dump of all of its contents. 17 | 18 | c. If the instruction is about applying a patch to a file: 19 | - Identify the target file, the operation (add or delete), and the patch content to be applied. 20 | - Create a YAML object with `type: patch`, and include the `targetFile`, `operation`, and `content` fields as needed. 21 | 22 | d. If the instruction is about displaying a message to the user: 23 | - Identify the message to be displayed. 24 | - Create a YAML object with `type: instruction` and include the `message` field with the identified message. 25 | 26 | 4. Arrange the YAML objects in the order of the natural language instructions. 27 | 28 | 5. Output the final YAML content, combining all the created YAML objects. 29 | 30 | For example, if the natural language instructions are: 31 | - Create a new directory called "project". 32 | - Navigate to the "project" directory. 33 | - Apply a patch to the "README.md" file to add a new line. 34 | - Display a message saying "Directory created, navigated, and patch applied." 35 | 36 | You would generate the following YAML content: 37 | 38 | ```yaml 39 | - type: commands 40 | commands: 41 | - mkdir project 42 | - cd project 43 | - type: patch 44 | targetFile: README.md 45 | operation: add 46 | content: | 47 | @@ -1,2 +1,3 @@ 48 | This is a README file. 49 | +New line added by the patch. 50 | - type: instruction 51 | message: Directory created, navigated, and patch applied. 52 | ``` 53 | ---------------------------------------- 54 | 55 | Understand what the user wants, and translate that into YAML Runner instructions. 56 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const yargs = require('yargs/yargs'); 4 | const { hideBin } = require('yargs/helpers'); 5 | const inquirer = require('inquirer'); 6 | const chalk = require('chalk'); 7 | const boxen = require('boxen'); 8 | 9 | const argv = 10 | yargs(hideBin(process.argv)) 11 | .command( 12 | 'b', 13 | 'Execute the beautiful CLI', 14 | (yargs) => { 15 | yargs 16 | .option('context', { 17 | alias: 'c', 18 | type: 'array', 19 | description: 'Specify context files', 20 | }) 21 | .option('e', { 22 | type: 'string', 23 | description: 'Execute a command', 24 | }); 25 | }, 26 | (argv) => { 27 | const boxenOptions = { 28 | padding: 1, 29 | margin: 1, 30 | borderStyle: 'round', 31 | borderColor: 'green', 32 | backgroundColor: '#555555', 33 | }; 34 | 35 | const welcomeMessage = boxen( 36 | chalk.green.bold('Welcome to the Beautiful CLI!'), 37 | boxenOptions 38 | ); 39 | 40 | console.log(welcomeMessage); 41 | 42 | inquirer 43 | .prompt([ 44 | { 45 | type: 'input', 46 | name: 'contextFiles', 47 | message: `Enter context files (${chalk.yellow( 48 | argv.context.join(', ') 49 | )}):`, 50 | }, 51 | { 52 | type: 'input', 53 | name: 'executeCommand', 54 | message: `Enter command to execute (${chalk.yellow(argv.e)}):`, 55 | }, 56 | ]) 57 | .then((answers) => { 58 | console.log( 59 | `${chalk.green.bold( 60 | 'Executing with context files:' 61 | )} ${answers.contextFiles}` 62 | ); 63 | console.log( 64 | `${chalk.green.bold('Executing command:')} ${answers.executeCommand}` 65 | ); 66 | }); 67 | } 68 | ) 69 | .help().argv;// Sets up the command-line interface for the Beautiful CLI.\n 70 | -------------------------------------------------------------------------------- /src/runner.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as child_process from 'child_process'; 3 | import * as yaml from 'js-yaml'; 4 | import * as path from 'path'; 5 | import * as os from 'os'; 6 | import * as diff from 'diff'; 7 | 8 | export const executeCommands = 9 | async (commands: string[]) => { 10 | const shellScriptPath = path.join(os.tmpdir(), 'temp_script.sh'); 11 | fs.writeFileSync(shellScriptPath, commands.join('\n')); 12 | 13 | try { 14 | const result = child_process.execSync(`bash ${shellScriptPath}`, { stdio: 'inherit' }); 15 | } catch (error: any) { 16 | console.error('Error executing commands:', error.message); 17 | } finally { 18 | fs.unlinkSync(shellScriptPath); 19 | } 20 | }; 21 | 22 | async function applyPatch(targetFile: string, operation: string, content: string) { 23 | try { 24 | const fileContent = fs.readFileSync(targetFile, 'utf-8'); 25 | const patches = diff.parsePatch(content) as diff.ParsedDiff[]; 26 | 27 | if (operation === 'add') { 28 | const patchedContent = patches.reduce((content, patch) => diff.applyPatch(content, patch), fileContent); 29 | fs.writeFileSync(targetFile, patchedContent); 30 | } else if (operation === 'delete') { 31 | const reversedPatches = patches.map(patch => { 32 | const { hunks } = patch; 33 | const reversedHunks = hunks.map(hunk => { 34 | const { oldStart, oldLines, newStart, newLines, lines } = hunk; 35 | return { 36 | oldStart: newStart, 37 | oldLines: newLines, 38 | newStart: oldStart, 39 | newLines: oldLines, 40 | lines: lines.map(line => { 41 | if (line.startsWith('-')) return '+' + line.slice(1); 42 | if (line.startsWith('+')) return '-' + line.slice(1); 43 | return line; 44 | }), 45 | }; 46 | }); 47 | return { ...patch, hunks: reversedHunks }; 48 | }); 49 | 50 | const patchedContent = reversedPatches.reduce((content, patch) => diff.applyPatch(content, patch), fileContent); 51 | fs.writeFileSync(targetFile, patchedContent); 52 | } else { 53 | console.error(`Invalid patch operation: ${operation}`); 54 | } 55 | } catch (error: unknown) { 56 | console.error(`Error applying patch: ${(error as Error).message}`); 57 | } 58 | } 59 | 60 | export const processYamlObjects = 61 | (yamlObjects: any[]) => { 62 | yamlObjects.forEach(async (obj) => { 63 | switch (obj.type) { 64 | case 'commands': 65 | await executeCommands(obj.commands); 66 | break; 67 | case 'instruction': 68 | console.log(obj.message); 69 | break; 70 | case 'file': 71 | if (obj.append) { 72 | fs.appendFileSync(obj.filename, obj.append); 73 | } else { 74 | fs.writeFileSync(obj.filename, obj.contents); 75 | } 76 | break; 77 | case 'patch': 78 | applyPatch(obj.targetFile, obj.operation, obj.content); 79 | break; 80 | default: 81 | console.error(`Invalid YAML object type: ${obj.type}`); 82 | break; 83 | } 84 | }); 85 | }; 86 | 87 | const yamlFilePath = process.argv[2]; 88 | 89 | if (!yamlFilePath) { 90 | console.error('Error: YAML file path is missing. Please provide a valid path as an argument.'); 91 | process.exit(1); 92 | } 93 | 94 | const yamlContent = fs.readFileSync(yamlFilePath, 'utf-8'); 95 | const yamlObjects = yaml.load(yamlContent); 96 | 97 | processYamlObjects( yamlObjects); 98 | -------------------------------------------------------------------------------- /test/1.yaml: -------------------------------------------------------------------------------- 1 | - type: instruction 2 | message: Create a directory, navigate to it, create a file, and then return to the original directory 3 | - type: commands 4 | commands: 5 | - mkdir test-dir 6 | - cd test-dir 7 | - touch test-file.txt 8 | - cd .. -------------------------------------------------------------------------------- /test/2.yaml: -------------------------------------------------------------------------------- 1 | - type: instruction 2 | message: Create a directory, navigate to it, create a file, list contents, and then remove the directory 3 | - type: commands 4 | commands: 5 | - mkdir test-dir 6 | - cd test-dir 7 | - touch test-file.txt 8 | - ls 9 | - cd .. 10 | - rm -r test-dir -------------------------------------------------------------------------------- /test/mkdir-cd.yaml: -------------------------------------------------------------------------------- 1 | - type: instruction 2 | message: Create a new directory for the project and navigate to it 3 | - type: commands 4 | commands: 5 | - mkdir ts-runner 6 | - cd ts-runner -------------------------------------------------------------------------------- /test/patch.yaml: -------------------------------------------------------------------------------- 1 | # Create a file named 'test.txt' with initial content 2 | - type: file 3 | filename: test.txt 4 | contents: | 5 | This is a test file. 6 | Line 2: Original content. 7 | 8 | # Apply a patch to the 'test.txt' file 9 | - type: patch 10 | targetFile: test.txt 11 | operation: add 12 | content: | 13 | @@ -1,2 +1,3 @@ 14 | This is a test file. 15 | +Line 1.5: New content. 16 | Line 2: Original content. 17 | 18 | # Display the updated content of the 'test.txt' file 19 | - type: commands 20 | commands: 21 | - cat test.txt 22 | 23 | # Output a message to indicate the process is complete 24 | - type: instruction 25 | message: Test file modified and displayed. -------------------------------------------------------------------------------- /test/planets.yaml: -------------------------------------------------------------------------------- 1 | - type: file 2 | filename: Mercury.txt 3 | contents: '' 4 | - type: file 5 | filename: Venus.txt 6 | contents: '' 7 | - type: file 8 | filename: Earth.txt 9 | contents: '' 10 | - type: file 11 | filename: Mars.txt 12 | contents: '' 13 | - type: file 14 | filename: Jupiter.txt 15 | contents: '' 16 | - type: file 17 | filename: Saturn.txt 18 | contents: '' 19 | - type: file 20 | filename: Uranus.txt 21 | contents: '' 22 | - type: file 23 | filename: Neptune.txt 24 | contents: '' -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true 7 | } 8 | } 9 | --------------------------------------------------------------------------------