Skip to content

Commit 19a8e60

Browse files
add config command
1 parent b1faeef commit 19a8e60

File tree

8 files changed

+624
-26
lines changed

8 files changed

+624
-26
lines changed

.devcontainer/devcontainer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"extensions": [
1010
"golang.go",
1111
"github.vscode-github-actions",
12-
"ms-azuretools.vscode-docker"
12+
"ms-azuretools.vscode-docker",
13+
"github.copilot"
1314
]
1415
}
1516
}

README.md

+1-5
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@ Using file names and natural language, Ghost generates a GitHub Action skeleton.
55

66
## Getting started
77
1. First, you'll need to set up an [OpenAI API key](https://platform.openai.com/account/api-keys).
8-
2. Set the API key in your environment variables as `OPENAI_API_KEY` (e.g. `export OPENAI_API_KEY=<your-key-here>`)
8+
2. Run `ghost config set OPENAI_API_KEY <your key here>` with your key from step 1
99
3. Run `ghost run` to start project analysis of the current working directory.
1010

11-
12-
## Upcoming features
13-
- Ability to set other models (currently uses GPT 3.5 Turbo) using a config file
14-
- VS Code extension

cmd/config.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/charmbracelet/log"
8+
"github.com/spf13/cobra"
9+
"github.com/spf13/viper"
10+
)
11+
12+
var configCmd = &cobra.Command{
13+
Use: "config",
14+
Short: "Configure Ghost CLI",
15+
}
16+
17+
var setCmd = &cobra.Command{
18+
Use: "set",
19+
Short: "Set a configuration for Ghost",
20+
Args: func(cmd *cobra.Command, args []string) error {
21+
if err := cobra.MinimumNArgs(2)(cmd, args); err != nil {
22+
return err
23+
}
24+
if args[0] == "OPENAI_API_KEY" {
25+
viper.Set(args[0], args[1])
26+
viper.WriteConfig()
27+
return nil
28+
}
29+
30+
return fmt.Errorf("invalid key: %s", args[0])
31+
},
32+
Run: func(cmd *cobra.Command, args []string) {
33+
34+
},
35+
}
36+
37+
type Config struct {
38+
OpenAIAPIKey string `mapstructure:"openai_api_key"`
39+
}
40+
41+
var getCmd = &cobra.Command{
42+
Use: "get",
43+
Short: "Get the configuration for Ghost",
44+
Run: func(cmd *cobra.Command, args []string) {
45+
home, _ := os.UserHomeDir()
46+
path := home + "/.ghost.yaml"
47+
config, err := loadConfig(path)
48+
49+
if err != nil {
50+
log.Fatal("cannot load config:", err)
51+
}
52+
fmt.Println("OPENAI_API_KEY=",config.OpenAIAPIKey)
53+
54+
},
55+
}
56+
57+
func init() {
58+
configCmd.AddCommand(setCmd, getCmd)
59+
}
60+
61+
func loadConfig(path string) (config Config, err error) {
62+
viper.AddConfigPath(path)
63+
viper.SetConfigName(".ghost")
64+
viper.SetConfigType("yaml")
65+
66+
viper.AutomaticEnv()
67+
68+
err = viper.ReadInConfig()
69+
if err != nil {
70+
return
71+
}
72+
73+
err = viper.Unmarshal(&config)
74+
return
75+
}

cmd/root.go

+39-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package cmd
22

33
import (
4+
"errors"
45
"fmt"
6+
"os"
7+
"path/filepath"
58

69
"github.com/enescakir/emoji"
710
"github.com/spf13/cobra"
11+
"github.com/spf13/viper"
812
)
913

1014
var (
15+
cfgFile string
1116
rootCmd = &cobra.Command{
1217
Use: "ghost",
1318
Short: fmt.Sprintf("\n%v Ghost is an experimental CLI that intelligently scaffolds a GitHub Action workflow based on your local application stack and natural language, using OpenAI.", emoji.Ghost),
@@ -19,6 +24,39 @@ func Execute() error {
1924
}
2025

2126
func init() {
27+
cobra.OnInitialize(InitConfig)
28+
29+
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
30+
rootCmd.PersistentFlags().Bool("viper", true, "use Viper for configuration")
31+
2232
rootCmd.CompletionOptions.HiddenDefaultCmd = true
23-
rootCmd.AddCommand(runCmd)
33+
rootCmd.AddCommand(runCmd, configCmd)
34+
}
35+
36+
func InitConfig() {
37+
if cfgFile != "" {
38+
viper.SetConfigFile(cfgFile)
39+
} else {
40+
configHome, err := os.UserHomeDir()
41+
configName := ".ghost"
42+
configType := "yaml"
43+
44+
cobra.CheckErr(err)
45+
viper.AddConfigPath(configHome)
46+
viper.SetConfigType(configType)
47+
viper.SetConfigName(configName)
48+
configPath := filepath.Join(configHome, configName+"."+configType)
49+
50+
51+
if _, err := os.Stat(configPath); err == nil {
52+
viper.AutomaticEnv()
53+
} else if errors.Is(err, os.ErrNotExist) {
54+
if err := viper.SafeWriteConfig(); err != nil {
55+
if err != nil {
56+
str := fmt.Sprintf("could not write config file: %v", err)
57+
panic(str)
58+
}
59+
}
60+
}
61+
}
2462
}

cmd/run.go

+17-6
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ import (
1212
"github.com/charmbracelet/bubbles/textinput"
1313
"github.com/charmbracelet/bubbles/viewport"
1414
tea "github.com/charmbracelet/bubbletea"
15+
1516
// "golang.org/x/term"
1617

1718
"github.com/charmbracelet/lipgloss"
1819
"github.com/charmbracelet/log"
1920
"github.com/enescakir/emoji"
2021
"github.com/sashabaranov/go-openai"
2122
"github.com/spf13/cobra"
23+
"github.com/spf13/viper"
2224
)
2325

2426
type model struct {
@@ -70,16 +72,24 @@ const (
7072

7173
var runCmd = &cobra.Command{
7274
Use: "run",
73-
Short: fmt.Sprintf("%v Run the Ghost CLI", emoji.Rocket),
75+
Short: "Run Ghost CLI",
7476
Run: func(cmd *cobra.Command, args []string) {
77+
err := viper.ReadInConfig()
78+
if err != nil {
79+
panic(fmt.Errorf("fatal error config file: %w", err))
80+
}
81+
82+
if !viper.IsSet("OPENAI_API_KEY") {
83+
log.Error("Please set OPENAI_API_KEY in your environment using `ghost config set OPENAI_API_KEY <your key>`")
84+
os.Exit(1)
85+
}
86+
7587
m := initialModel()
7688
p := tea.NewProgram(m, tea.WithAltScreen(), tea.WithMouseAllMotion())
77-
7889
if _, err := p.Run(); err != nil {
7990
log.Fatal("Yikes! We've run into a problem: ", err)
8091
os.Exit(1)
8192
}
82-
8393
},
8494
}
8595

@@ -98,7 +108,6 @@ func initialModel() model {
98108
additionalInfo.CharLimit = 300
99109
additionalInfo.Width = 300
100110

101-
// width, height, _ := term.GetSize(0)
102111
vp := viewport.New(0, 0)
103112
vp.Style = viewportStyle
104113
vp.YPosition = 0
@@ -171,7 +180,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
171180
m.choice = "yes"
172181
}
173182
// View to allow the user to specify what tasks they'd like to do in their pipeline
174-
case InputTasks, CorrectGHA :
183+
case InputTasks, CorrectGHA:
175184
if m.desiredTasks.Value() != "" {
176185
m.currentView = LoadingGHA
177186
cmds = append(cmds, func() tea.Msg {
@@ -202,6 +211,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
202211
default:
203212
switch m.currentView {
204213
case Preload:
214+
205215
if len(m.files) == 0 {
206216
m.files = getFilesInCurrentDirAndSubDirs()
207217
}
@@ -300,7 +310,8 @@ func (m model) View() string {
300310
type gptResponse string
301311

302312
func chatGPTRequest(prompt string) (response gptResponse, err error) {
303-
apiKey := os.Getenv("OPENAI_API_KEY")
313+
viper.ReadInConfig()
314+
apiKey := viper.GetString("openai_api_key")
304315
client := openai.NewClient(apiKey)
305316
resp, err := client.CreateChatCompletion(
306317
context.Background(),

go.mod

+19-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,25 @@ go 1.20
44

55
require github.com/sashabaranov/go-openai v1.9.5
66

7-
require github.com/enescakir/emoji v1.0.0
7+
require (
8+
github.com/enescakir/emoji v1.0.0
9+
github.com/spf13/viper v1.16.0
10+
)
811

9-
require github.com/atotto/clipboard v0.1.4 // indirect
12+
require (
13+
github.com/atotto/clipboard v0.1.4 // indirect
14+
github.com/fsnotify/fsnotify v1.6.0 // indirect
15+
github.com/hashicorp/hcl v1.0.0 // indirect
16+
github.com/magiconair/properties v1.8.7 // indirect
17+
github.com/mitchellh/mapstructure v1.5.0 // indirect
18+
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
19+
github.com/spf13/afero v1.9.5 // indirect
20+
github.com/spf13/cast v1.5.1 // indirect
21+
github.com/spf13/jwalterweatherman v1.1.0 // indirect
22+
github.com/subosito/gotenv v1.4.2 // indirect
23+
gopkg.in/ini.v1 v1.67.0 // indirect
24+
gopkg.in/yaml.v3 v3.0.1 // indirect
25+
)
1026

1127
require (
1228
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
@@ -29,7 +45,7 @@ require (
2945
github.com/spf13/cobra v1.7.0
3046
github.com/spf13/pflag v1.0.5 // indirect
3147
golang.org/x/sync v0.1.0 // indirect
32-
golang.org/x/sys v0.7.0 // indirect
48+
golang.org/x/sys v0.8.0 // indirect
3349
golang.org/x/term v0.7.0 // indirect
3450
golang.org/x/text v0.9.0 // indirect
3551
)

0 commit comments

Comments
 (0)