ChatGPT in the Shell
Tags: openai, chatgpt, go, cli
Published: 2023-03-25 14:17:37

**Introduction:**
_Hello there amigo :wave:._

In this tutorial, I will show you how to create a CLI app that allows you to interact with ChatGPT from the command line. The app, named "ChatShell", will allow users to ask for shell commands for specific tasks or chat with ChatGPT directly. We'll be using the Go programming language and Cobra CLI library to create our CLI app and the go-openai library to interact with OpenAI's GPT-3 API.

Please do give a thumbs-up, if this was insightful in any way.

**Prerequisites:**

- Go programming language installed (version 1.20.2 or higher)
- Cobra CLI library installed
- go-openai library installed
- Viper library installed (for config and environment vars)
- An OpenAI API key

**End Goal:**

- Able to run shell command of form:
``` bash
chatshell ask "how to delete a local branch ?"
```
``` bash
chatshell ask -c "when was gpt first built ?"
```

**Steps:**

**A. Set up the project structure**

_Create a new directory for the project and navigate to it_

```shell
$ mkdir chatshell
$ cd chatshell
```

_Initialize a new Go module_

```shell
$ go mod init github.com/{your_username}/chatshell
# note: replace {your_username} with actual github username
```

_Install the Cobra CLI library and go-openai library_


```shell
$ go install github.com/spf13/cobra-cli@latest
$ go get -u github.com/sashabaranov/go-openai
$ go get github.com/spf13/viper
```

**B. Create the CLI app**

cobra-cli helps generate boilerplates for your cli app automatically, this speeds up development.

Use the Cobra CLI to generate the basic structure of the CLI app:

```shell
$ cobra-cli init
```

This will generate the basic structure of the CLI app, including a main.go file, a cmd directory, and a LICENSE file.

You can have a look at the code if you're familiar with cobra.


![Project Structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ns4ukqrei74ow5ulwoix.png)

**C. Implement the 'ask' command**

Once again we will call on our friend (cobra-cli)

```bash 
cobra-cli add ask
```

This will generate a file ask.go which will contain the implementation of the 'ask' command. We have to finetune the boilerplate to our need, below is finetuned code well commented for understanding.


Import necessary packages

```golang
/*
Copyright © 2023 Ugochukwu Onyebuchi <pyvinci@gmail.com>
*/
package cmd

import (
	"context"
	"fmt"
	"os"
	"runtime"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	openai "github.com/sashabaranov/go-openai"
)
```

Next intialize the askCmd
Use: a short description of how to use the ask command, in our case we expect ask to be followed by a text in quotes
Short: a short description of the ask command
Long: a longer description
Args: we specify we only want one argument, which is a text in quotes
Run: we specify the function to be called, this function will handle the business logic.

```golang
// askCmd represents the ask command
var askCmd = &cobra.Command{
	Use:   "ask [text]",
	Short: "ask ChatGPT",
	Long:  `ask is used to ask for shell commands for a particular task`,
	Args:  cobra.ExactArgs(1),
	Run:   runAsk,
}

// authToken holds your openai auth key
var authToken string
```


``` golang
// initial operations
func init() {
        // add the ask command to root
	rootCmd.AddCommand(askCmd)
        
        // add flag to enable chat mode
	askCmd.PersistentFlags().BoolP("chat", "c", false, "Chat with ChatGPT")

	// Read the auth token from a configuration file
	viper.SetConfigName("config")
	viper.AddConfigPath("$HOME/.chatshell")
	viper.AddConfigPath(`%USERPROFILE%/.chatshell`)
	viper.SetEnvPrefix("OPENAI")
	viper.AutomaticEnv()

        // Handle missing config file or auth token
	if err := viper.ReadInConfig(); err == nil {
		authToken = viper.GetString("OPENAI_AUTH_TOKEN")
	} else {
		fmt.Printf("Error reading configuration file: %v\n", err)
	}

	if authToken == "" {
		fmt.Println("Error: OPENAI_AUTH_TOKEN environment variable not set")
		os.Exit(1)
	}
}
```

The prompt I use is a bit large: but for much correctness I get the osType using `runtime.GOOS` and SHELL type using `os.Getenv("SHELL")`, this will help ChatGPT give reasonable shell commands of the relevant OS, we don't want shell commands for mac whilst we are using windows.. and it also responds with "Sorry I can't seem to find a command for that", if it can find a command for your prompt. It took a while to get a good prompt as this, it's an iterative process..

``` golang
// Call OpenAI and prints response
func runAsk(cmd *cobra.Command, args []string) {
	client := openai.NewClient(authToken)
	osType := runtime.GOOS
	shell := os.Getenv("SHELL")
	content := ""

	chatMode, _ := cmd.Flags().GetBool("chat")
        
        // We use the state of the flag to determine the context
	if !chatMode {
        content = fmt.Sprintf(`You are a very helpful shell assistant that gives users only shell commands to achieve a task, just give out only the shell command(s), and nothing else, no preamble, greetings or explanation please, just the shell command. When you can't find a command for a query/prompt/greeting respond strictly with "Sorry I can't seem to find a command for that". Start now: "%v in %v os using %v shell"`, args[0], osType, shell)
    } else {
        content = args[0]
    }
        // Get ChatGPT Response
	resp, err := client.CreateChatCompletion(
		context.Background(),
		openai.ChatCompletionRequest{
			Model: openai.GPT3Dot5Turbo,
			Messages: []openai.ChatCompletionMessage{
				{
					Role: openai.ChatMessageRoleUser,
					Content: content,
				},
			},
		},
	)

	if err != nil {
		fmt.Printf("ChatCompletion error: %v\n", err)
	}

	fmt.Println(resp.Choices[0].Message.Content)
}

```


**D. Update the configuration file**
For all this to work you will need your OPENAI_AUTH_TOKEN set.
Remember, we set viper to look for a config file that should contain our OPENAI_AUTH_TOKEN

Create a new directory called .chatshell in your home directory (on Windows, use %USERPROFILE% instead of $HOME):

```shell
$ mkdir ~/.chatshell
```

Create a new file called config.json in the .chatshell directory and add your OpenAI API key as the value for the OPENAI_AUTH_TOKEN variable:

```json
{"OPENAI_AUTH_TOKEN":"your_api_key_here"}
```

**E. Build the CLI app**
Navigate to the project's root directory and build the app:

```shell
$ go build
```

This will create an executable binary called 'chatshell'.

**F. Use the ChatShell CLI app**
Run the chatshell binary and provide a task description as an argument:

```shell
$ ./chatshell ask "how to delete a local branch ?"
```

```shell
output: git branch -d <branch_name>
```
The app will then interact with ChatGPT and return the shell command for the given task. 

You can also use the -c flag to chat with ChatGPT directly:

```shell
$ ./chatshell ask -c "When was gpt first built?"
```

```shell
output: GPT (Generative Pre-trained Transformer) was first introduced by OpenAI in June 2018
```

Conclusion:

In this tutorial, we saw how to create a Conda CLI app that interacts with ChatGPT from the command line. The ChatShell app can be used to obtain shell commands for specific tasks or chat with ChatGPT directly. Feel free to explore the provided code and the go-openai library to extend the functionality of the app or adapt it to your needs.

Repository: https://github.com/ColeDrain/chatshell