├── .github └── workflows │ └── go.yml ├── .gitignore ├── README.md └── main.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v3 21 | with: 22 | go-version: 1.19 23 | 24 | - name: Build 25 | run: go build -v -o dist/gitgpt main.go 26 | 27 | - name: Publish artifact 28 | uses: actions/upload-artifact@v3 29 | with: 30 | name: gitgpt 31 | path: dist/gitgpt 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | python_version/* 2 | .DS_Store 3 | dist/* 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Natural Language Git CLI assistant 2 | 3 | Interfacing with git sucks. With gitgpt you can use natural langauge instead of git commands to do what you want. 4 | 5 | Example: 6 | ``` 7 | $ ./gitgpt add .gitignore commit with msg adding ignore and push 8 | Would you like to run the following command: 9 | 10 | git add .gitignore 11 | git commit -m "adding ignore" 12 | git push [y/N] y 13 | [main c70490c] adding ignore 14 | 1 file changed, 3 insertions(+) 15 | create mode 100644 .gitignore 16 | Enumerating objects: 4, done. 17 | Counting objects: 100% (4/4), done. 18 | Delta compression using up to 12 threads 19 | Compressing objects: 100% (2/2), done. 20 | Writing objects: 100% (3/3), 312 bytes | 312.00 KiB/s, done. 21 | Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 22 | To https://github.com/Hesse/gitgpt.git 23 | 3a5828e..c70490c main -> main 24 | ``` 25 | 26 | 27 | 28 | ## Requirements 29 | 30 | - You must have an OpenAI API key. This key should be set as an environment vairable OPENAI_API_KEY. 31 | 32 | ``` 33 | export OPENAI_API_KEY= 34 | ``` 35 | 36 | - You must have go installed in order to build the source OR you can download the pre-built binary 37 | 38 | I have only tested this on Mac OS, however I'm pretty sure it'll work without issue on Linux as well. I can't say the same about Windows because I haven't tested it. 39 | 40 | 41 | ## Build / Installation 42 | 43 | ### Build 44 | 45 | 1. Clone the repo 46 | 2. go build -o dist/gitgpt main.go 47 | 3. Add the file to your PATH 48 | ``` 49 | echo 'export PATH=$PATH:' >> ~/.bash_profile 50 | 51 | ``` 52 | 4. Start using gitgpt in you shell! 53 | 54 | ## Usage 55 | 56 | ### Examples 57 | 58 | $ gitgpt create a new branch called feature/test add all the files and commit with msg creating feature test then push to origin 59 | 60 | ``` 61 | $ gitgpt create a new branch called feature/test add all the files and commit with msg creating feature test then push to origin 62 | Would you like to run the following command: 63 | 64 | git branch feature/test 65 | git add . 66 | git commit -m "creating feature test" 67 | git push origin feature/test [y/N] 68 | ``` 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | // "flag" 6 | "fmt" 7 | "net/http" 8 | "os" 9 | "os/exec" 10 | "regexp" 11 | "strings" 12 | ) 13 | 14 | type CompletionRequest struct { 15 | Model string `json:"model"` 16 | Prompt string `json:"prompt"` 17 | MaxTokens int `json:"max_tokens"` 18 | Temperature float64 `json:"temperature"` 19 | TopP float64 `json:"top_p"` 20 | Echo bool `json:"echo"` 21 | } 22 | 23 | type CompletionResponse struct { 24 | Choices []struct { 25 | Text string `json:"text"` 26 | } `json:"choices"` 27 | } 28 | 29 | func main() { 30 | apiKey := os.Getenv("OPENAI_API_KEY") 31 | if apiKey == "" { 32 | fmt.Fprintln(os.Stderr, "Missing OpenAI API key") 33 | os.Exit(1) 34 | } 35 | 36 | var prompt string 37 | if len(os.Args) > 1 { 38 | prompt = strings.Join(os.Args[1:], " ") 39 | } else { 40 | fmt.Fprintln(os.Stderr, "Usage: ./myprogram.go ") 41 | os.Exit(1) 42 | } 43 | 44 | prompt = "Provide only the appropriate git commands for:" + prompt 45 | 46 | reqBody := CompletionRequest{ 47 | Model: "text-davinci-003", 48 | Prompt: prompt, 49 | MaxTokens: 60, 50 | Temperature: 0.5, 51 | TopP: 1.0, 52 | Echo: false, 53 | } 54 | reqJSON, err := json.Marshal(reqBody) 55 | if err != nil { 56 | fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) 57 | os.Exit(1) 58 | } 59 | 60 | req, err := http.NewRequest("POST", "https://api.openai.com/v1/completions", strings.NewReader(string(reqJSON))) 61 | if err != nil { 62 | fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) 63 | os.Exit(1) 64 | } 65 | req.Header.Set("Content-Type", "application/json") 66 | req.Header.Set("Authorization", "Bearer "+apiKey) 67 | 68 | client := &http.Client{} 69 | resp, err := client.Do(req) 70 | if err != nil { 71 | fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) 72 | os.Exit(1) 73 | } 74 | defer resp.Body.Close() 75 | 76 | if resp.StatusCode != http.StatusOK { 77 | fmt.Fprintf(os.Stderr, "Error: %s\n", resp.Status) 78 | os.Exit(1) 79 | } 80 | 81 | var respBody CompletionResponse 82 | if err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil { 83 | fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) 84 | os.Exit(1) 85 | } 86 | 87 | text := respBody.Choices[0].Text 88 | 89 | codeRegex := regexp.MustCompile("`([^`]*)`") 90 | match := codeRegex.FindStringSubmatch(text) 91 | if match != nil { 92 | code := match[1] 93 | runWithConfirmation(code) 94 | } else { 95 | runWithConfirmation(text) 96 | } 97 | } 98 | 99 | func runWithConfirmation(command string) { 100 | fmt.Printf("Would you like to run the following command: %s [y/N] ", command) 101 | var confirmation string 102 | fmt.Scanln(&confirmation) 103 | 104 | if strings.ToLower(confirmation) == "y" { 105 | cmd := exec.Command("sh", "-c", command) 106 | cmd.Stdout = os.Stdout 107 | cmd.Stderr = os.Stderr 108 | err := cmd.Run() 109 | if err != nil { 110 | fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) 111 | os.Exit(1) 112 | } 113 | } else { 114 | fmt.Println("Command not executed.") 115 | } 116 | } 117 | --------------------------------------------------------------------------------