Creating An OpenAI Powered Script Runner with ChatGPT

Mar 28, 2023·6 min read

In the past few weeks I’ve spent some time trying to familiarize myself with OpenAI’s GPT models, and building products around them. Inspired by this Twitter thread, I decided to go a step further and build a tool I could use in my workflows.

Before diving in, a common use case for these models has been prompting them to write code in languages that you are not familiar with. In the linked tweet, the user prompts the model to write a bash script for a fairly repetitive task. Once the script has been verified he can proceed to use it at his convenience, essentially automating away that task.

Inspired by this, I decided to create a tool that would let me make other tools on the fly. To properly define this, I wanted a CLI tool which would generate, manage and run scripts written by GPT-4.

Designing the Interface

Before building this kind of interface its important to design how it should work. The design process lets you chart how the interface should effect the underlying system. Intuitively I also felt this would make the process of working with ChatGPT much smoother as it would only need to code the specification I’d provided it.

Golang is my language of choice for writing CLIs so I already had a pretty good understanding of what libraries I wanted to use.

I would instruct ChatGPT to use these libraries towards my target interface.

Commands:
  configure   Configure API key for OpenAI
  help        Help about any command
  list        List all scripts in the script directory
  make        Generate an executable script based on OpenAI chat completion
  read        Display the contents of a script
  remove      Remove an existing script
  reset       Remove all scripts and the API key
  run         Execute a script generated by 'dotsh make'

Converting the Interface to Code

In my experience using ChatGPT, I’ve found it is much more consistent given smaller more focused tasks, rather than large abstract, or open-ended ones. Don’t quote me here but anecdotally, the fewer thinking-decisions the model has to make, the better its output.

With this in mind, I designed my project folder before hand and began using ChatGPT on a per command basis.

├── go.mod
├── go.sum
├── main.go
├── cmd
│   ├── configure.go
│   ├── list.go
│   ├── make.go
│   ├── read.go
│   ├── remove.go
│   ├── reset.go
│   ├── root.go
│   ├── run.go

Majority of the commands follow the same format, but one worth special attention is the make command. This is where the meat of the implementation happens.

The Make Command

The make command parses the user’s input and makes a request to OpenAI to generate a script. The generated script is then saved to a file on the disk. To break this down algorithmically, the following steps are required:

  1. Receive user input, which includes the name of the script and its purpose
  2. Coerce the script’s purpose to get the best possible response from OpenAI
  3. Make a request to OpenAI, using GPT-4. If the request is successful then save the response to a file.

Coercing the script is necessary to ensure consistency when interacting with OpenAI. This is required to prevent the command from saving files which may not be valid. An example coercion which can be performed is wrapping the user input with some context.

With the updated input, the request can now be made and the response saved to a file. In order to run the script later, the file will need to be granted the appropriate permissions. For example:

err = os.Chmod(scriptFilePath, 0700)

The Other Commands

Other commands are much more similar. For example the configure command is generated with the following prompt:

“Write a command configure, in Golang (using the cobra library). This command should prompt the user for their OpenAI API key, and save the value to a file '.key', in the internal folder. Use the gopass library to get the user input. Assume a variable internalFolder is already defined, and only output the configureCmd

This produces the following output, which can then be tweaked to your liking and saved to the relevant file (cmd/configure.go).

configureCmd := &cobra.Command{
		Use:   "configure",
		Short: "Configure OpenAI API key",
		Long:  "Configure OpenAI API key",
		Run: func(cmd *cobra.Command, args []string) {
			// Prompt the user for their OpenAI API key using gopass
			fmt.Print("Enter your OpenAI API key: ")
			keyBytes, err := gopass.GetPasswdMasked()
			if err != nil {
				fmt.Fprintln(os.Stderr, "Error getting password:", err)
				os.Exit(1)
			}
			key := string(keyBytes)

			// Create the internal folder if it doesn't exist
			if _, err := os.Stat(internalFolder); os.IsNotExist(err) {
				os.Mkdir(internalFolder, 0700)
			}

			// Save the key to the file '.key' in the internal folder
			keyFile := internalFolder + "/.key"
			file, err := os.Create(keyFile)
			if err != nil {
				fmt.Fprintln(os.Stderr, "Error creating key file:", err)
				os.Exit(1)
			}
			defer file.Close()
			_, err = file.WriteString(key)
			if err != nil {
				fmt.Fprintln(os.Stderr, "Error writing key to file:", err)
				os.Exit(1)
			}
			fmt.Println("API key saved successfully")
		},
	}

Conclusion

I’ve been using the resulting program for a few days and I’m very impressed at how capable it is with just a few steps and a few hours of effort. In practice you should be weary of running scripts you don’t understand on your computer as this could lead to all sorts of vulnerabilities. A fun command to add could be an explain command which will describe what a given script does line by line.

The final program looks very similar to our designed interface, with a few additional items provided by Cobra, such as this output when running the help command.

➜  ~ dotsh
A CLI tool for generating and running executable scripts based on OpenAI chat completion

Usage:
  dotsh [command]

Available Commands:
  configure   Configure API key for OpenAI
  help        Help about any command
  list        List all scripts in the script directory
  make        Generate an executable script based on OpenAI chat completion
  read        Display the contents of a script
  remove      Remove an existing script
  reset       Remove all scripts and the API key
  run         Execute a script generated by 'dotsh make'

Flags:
  -h, --help   help for dotsh

Use "dotsh [command] --help" for more information about a command. 

Using dotsh I can now generate scripts on the fly as I need them.

➜  ~ dotsh make "git-head" "get the latest commit sha from within a git repo directory and limit the sha to 6 characters"
Generated script 'git-head.sh'
➜  ~ dotsh read "git-head"
#!/bin/bash
git rev-parse --short=6 HEAD
➜  ~ dotsh run "git-head"
8ff2b9

I’m excited to keep diving deeper with a few more projects to get a better handle on the limits, and capabilities of these models.