-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
128 lines (112 loc) · 2.82 KB
/
main.go
File metadata and controls
128 lines (112 loc) · 2.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// commitmsg.go
package main
import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
"os"
"os/exec"
"strings"
"time"
)
const (
defaultModel = "gpt-4o-mini" // wähle nach Bedarf
apiURL = "https://api.openai.com/v1/chat/completions"
maxDiffBytes = 200_000
httpTimeoutSeconds = 25
)
func main() {
if len(os.Args) < 2 {
fail("usage: commitmsg <path-to-message-file>")
}
msgPath := os.Args[1]
diff, err := stagedDiff()
if err != nil {
fail("git diff failed: " + err.Error())
}
if len(diff) == 0 {
os.Exit(0)
}
if len(diff) > maxDiffBytes {
diff = diff[:maxDiffBytes]
}
apiKey := os.Getenv("OPENAI_API_KEY")
if apiKey == "" {
fail("OPENAI_API_KEY not set")
}
msg, err := callOpenAI(apiKey, defaultModel, buildPrompt(diff))
if err != nil || strings.TrimSpace(msg) == "" {
msg = "chore: update changes"
}
if err := os.WriteFile(msgPath, []byte(strings.TrimSpace(msg)+"\n"), 0644); err != nil {
fail("write message failed: " + err.Error())
}
}
func stagedDiff() (string, error) {
cmd := exec.Command("git", "diff", "--cached")
out, err := cmd.Output()
return string(out), err
}
func buildPrompt(diff string) string {
return "Create a Commit-Message following Conventional Commits.\n" +
"English. Subject max. 72 sign, optional body with explanation, without Markdown, only plain text.\n\nDiff:\n" +
diff
}
// -------- OpenAI --------
type oaMsg struct {
Role string `json:"role"`
Content string `json:"content"`
}
type oaReq struct {
Model string `json:"model"`
Messages []oaMsg `json:"messages"`
}
type oaResp struct {
Choices []struct {
Message struct {
Content string `json:"content"`
} `json:"message"`
} `json:"choices"`
}
func callOpenAI(apiKey, model, prompt string) (string, error) {
body, _ := json.Marshal(oaReq{
Model: model,
Messages: []oaMsg{
{Role: "system", Content: "You write excellent Commit-Messages."},
{Role: "user", Content: prompt},
},
})
req, _ := http.NewRequest("POST", apiURL, bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{
Timeout: httpTimeoutSeconds * time.Second,
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
},
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
b, _ := io.ReadAll(resp.Body)
return "", errors.New("api status: " + resp.Status + " body: " + string(b))
}
var parsed oaResp
if err := json.NewDecoder(resp.Body).Decode(&parsed); err != nil {
return "", err
}
if len(parsed.Choices) == 0 {
return "", errors.New("no choices")
}
return parsed.Choices[0].Message.Content, nil
}
// -------- utils --------
func fail(msg string) {
os.Stderr.WriteString(msg + "\n")
os.Exit(1)
}