If you encounter any issues through this tutorial, you can see the add-joker branch for an implementation
- Initialize
go.mod
:go mod init github.com/dfreilich/gophercon-cli
- Create a cmd directory:
mkdir -p cmd/
- Get cobra:
go get github.com/spf13/cobra
- Create a file in the
cmd/
directory with the contents:
func NewJokerCmd() *cobra.Command {
return &cobra.Command{
Use: "joker",
Aliases: []string{"joke"},
Short: "This returns GPT3 Dad jokes!",
Version: "0.0.1",
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("Hello Gophercon!")
return nil
},
}
}
- Initialize the
main.go
file to run the command:
func main() {
root := cmd.NewJokerCmd()
if err := root.Execute(); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
- Create a
Makefile
with the contents:
GOCMD?=go
NAME?=joker
build:
$(GOCMD) build -o $(NAME)
test:
$(GOCMD) test ./... -v
run:
$(GOCMD) run ./...
.PHONY: build test run
- Write initial test in
root_test.go
:
func TestNewJokerCmd(t *testing.T) {
cmd := NewJokerCmd()
buf := &bytes.Buffer{}
cmd.SetOut(buf)
err := cmd.Execute()
require.NoError(t, err)
require.NotEmpty(t, buf.String())
}
- Get library for
GPT3
:go get github.com/sashabaranov/go-gpt3
- Make
API Key
here and set it as an environment variableexport OPEN_AI_KEY=MY_KEY
- Use GPT3 in
root.go
:
// Note: For this, you need to make an API KEY at https://beta.openai.com/account/api-keys
c := gogpt.NewClient(os.Getenv("OPEN_AI_KEY"))
ctx := context.Background()
req := gogpt.CompletionRequest{
Model: gogpt.GPT3TextDavinci003,
MaxTokens: maxTokens,
Temperature: 1,
Prompt: "Tell me a corny dad joke",
TopP: 1,
FrequencyPenalty: 1,
PresencePenalty: 1,
}
resp, err := c.CreateCompletion(ctx, req)
if err != nil {
return err
}
- Get a nice rendering library:
go get github.com/charmbracelet/lipgloss
- Make the output ✨ fabulous ✨ :
style := lipgloss.NewStyle().
Bold(true).
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("12")). // Light Blue
Foreground(lipgloss.Color("5")) // Magenta
fmt.Println(style.Render(strings.TrimSpace(resp.Choices[0].Text)))
- Go get assertion library:
go get github.com/stretchr/testify/require
- Install
mockgen
:go install github.com/golang/mock/[email protected]
andgo get github.com/golang/mock/mockgen/model
- Create the interface and annotations:
//go:generate mockgen -package mocks -destination ../test/mocks/mock_asker.go github.com/dfreilich/gophercon-cli/cmd Asker
type Asker interface {
CreateCompletion(ctx context.Context, request gogpt.CompletionRequest) (response gogpt.CompletionResponse, err error)
}
- Change the Command to use it:
func NewJokerCmd(asker Asker) *cobra.Command {
...
resp, err := asker.CreateCompletion(ctx, req)
- Change the main to se it:
c := gogpt.NewClient(os.Getenv("OPEN_AI_KEY"))
root := cmd.NewJokerCmd(c)
- Change the test to use it:
func TestNewJokerCmd(t *testing.T) {
ctrl := gomock.NewController(t)
testActor := mocks.NewMockAsker(ctrl)
cmd := NewJokerCmd(testActor)
testActor.EXPECT().CreateCompletion(gomock.Any(), gomock.Any()).Return(gogpt.CompletionResponse{
Choices: []gogpt.CompletionChoice{{Text: "Some funny joke!"}},
}, nil)
buf := &bytes.Buffer{}
cmd.SetOut(buf)
err := cmd.Execute()
require.NoError(t, err)
require.NotEmpty(t, buf.String())
require.Contains(t, buf.String(), "Some funny joke!")
}
For more, check out the Cobra documentation here, and look at how some major CLIs are using it.
One project you can check out is the Cloud Native Buildpacks pack CLI. You can see how they generate the Root Command here, define subcommands here, and create an interface of their client here