Creating An OpenAI Powered Script Runner with ChatGPT
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.
- Cobra: For declaring the commands interface and handlers
- rakyll/openai-go: A golang wrapper for OpenAI’s API
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:
- Receive user input, which includes the name of the script and its purpose
- Coerce the script’s purpose to get the best possible response from OpenAI
- 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.
- Input: “checks the weather”
- Input after coercion: “Write a bash script which does the following: ‘checks the weather’. I only want the code in a valid .sh file format.”
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 variableinternalFolder
is already defined, and only output theconfigureCmd
”
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.