refactor(cli): simplify commands and remove TUI dependencies
- Switch chat command from Bubble Tea TUI to basic CLI with bufio. - Hardcode Grok model in commands, remove Viper config. - Stream responses directly in client, remove channel-based streaming. - Add safe previews/backups in edit, simplify prompts across tools. - Update git helper and trim unused deps in go.mod.
This commit is contained in:
parent
917bab2adc
commit
f0858e08c1
194
cmd/chat.go
194
cmd/chat.go
@ -1,185 +1,43 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/spinner"
|
|
||||||
"github.com/charmbracelet/bubbles/textinput"
|
|
||||||
"github.com/charmbracelet/bubbles/viewport"
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
|
||||||
"gmgauthier.com/grokkit/internal/grok"
|
"gmgauthier.com/grokkit/internal/grok"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
|
||||||
Message struct {
|
|
||||||
Role string
|
|
||||||
Content string
|
|
||||||
}
|
|
||||||
model struct {
|
|
||||||
messages []Message
|
|
||||||
history []map[string]string
|
|
||||||
input textinput.Model
|
|
||||||
spinner spinner.Model
|
|
||||||
viewport viewport.Model
|
|
||||||
streamCh <-chan string
|
|
||||||
streaming bool
|
|
||||||
client *grok.Client
|
|
||||||
gmodel string
|
|
||||||
}
|
|
||||||
streamTickMsg struct{}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
userStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("172")).
|
|
||||||
Padding(0, 1)
|
|
||||||
grokStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("45")).
|
|
||||||
Padding(0, 1)
|
|
||||||
headerStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("99")).
|
|
||||||
Bold(true)
|
|
||||||
)
|
|
||||||
|
|
||||||
func initialModel() *model {
|
|
||||||
systemContent := "You are Grok, a helpful and maximally truthful AI built by xAI, not based on any other companies and their models."
|
|
||||||
system := Message{Role: "system", Content: systemContent}
|
|
||||||
h := []map[string]string{{"role": "system", "content": systemContent}}
|
|
||||||
ti := textinput.New()
|
|
||||||
ti.Placeholder = "Type message (Enter send, q/Ctrl+C quit)"
|
|
||||||
ti.Focus()
|
|
||||||
sp := spinner.New()
|
|
||||||
sp.Spinner = spinner.Dot
|
|
||||||
sp.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("63"))
|
|
||||||
vp := viewport.New(0, 0)
|
|
||||||
vp.MouseWheelEnabled = true
|
|
||||||
vp.GotoBottom()
|
|
||||||
return &model{
|
|
||||||
messages: []Message{system},
|
|
||||||
history: h,
|
|
||||||
input: ti,
|
|
||||||
spinner: sp,
|
|
||||||
viewport: vp,
|
|
||||||
client: grok.NewClient(),
|
|
||||||
gmodel: viper.GetString("model"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m model) renderMessages() string {
|
|
||||||
var lines []string
|
|
||||||
for _, msg := range m.messages {
|
|
||||||
content := strings.ReplaceAll(msg.Content, "\n", "\n ")
|
|
||||||
switch msg.Role {
|
|
||||||
case "user":
|
|
||||||
lines = append(lines, userStyle.Render("You: "+content))
|
|
||||||
case "assistant":
|
|
||||||
lines = append(lines, grokStyle.Render("Grok: "+content))
|
|
||||||
case "system":
|
|
||||||
lines = append(lines, content)
|
|
||||||
}
|
|
||||||
lines = append(lines, "")
|
|
||||||
}
|
|
||||||
return strings.Join(lines, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func streamTickMsgCmd() tea.Cmd {
|
|
||||||
return func() tea.Msg {
|
|
||||||
return streamTickMsg{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
var cmds []tea.Cmd
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case tea.WindowSizeMsg:
|
|
||||||
m.viewport.Width = msg.Width
|
|
||||||
if msg.Height > 3 {
|
|
||||||
m.viewport.Height = msg.Height - 3
|
|
||||||
}
|
|
||||||
m.viewport.SetContent(m.renderMessages())
|
|
||||||
return m, nil
|
|
||||||
case tea.KeyMsg:
|
|
||||||
if m.streaming {
|
|
||||||
if msg.String() == "q" || msg.String() == "ctrl+c" || msg.String() == "esc" {
|
|
||||||
return m, tea.Quit
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
switch msg.String() {
|
|
||||||
case "q", "ctrl+c", "esc":
|
|
||||||
return m, tea.Quit
|
|
||||||
case "enter":
|
|
||||||
text := m.input.Value()
|
|
||||||
if text == "" {
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
userMsg := Message{Role: "user", Content: text}
|
|
||||||
m.messages = append(m.messages, userMsg)
|
|
||||||
m.history = append(m.history, map[string]string{"role": "user", "content": text})
|
|
||||||
assMsg := Message{Role: "assistant", Content: ""}
|
|
||||||
m.messages = append(m.messages, assMsg)
|
|
||||||
m.input.Reset()
|
|
||||||
m.streaming = true
|
|
||||||
m.streamCh = m.client.StreamChan(m.history, m.gmodel)
|
|
||||||
m.viewport.SetContent(m.renderMessages())
|
|
||||||
cmds = append(cmds, streamTickMsgCmd())
|
|
||||||
return m, tea.Batch(cmds...)
|
|
||||||
default:
|
|
||||||
var cmd tea.Cmd
|
|
||||||
m.input, cmd = m.input.Update(msg)
|
|
||||||
return m, cmd
|
|
||||||
}
|
|
||||||
case streamTickMsg:
|
|
||||||
if !m.streaming || m.streamCh == nil {
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case chunk, ok := <-m.streamCh:
|
|
||||||
if !ok {
|
|
||||||
m.streaming = false
|
|
||||||
m.streamCh = nil
|
|
||||||
m.viewport.SetContent(m.renderMessages())
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
m.messages[len(m.messages)-1].Content += chunk
|
|
||||||
m.viewport.SetContent(m.renderMessages())
|
|
||||||
m.viewport.GotoBottom()
|
|
||||||
return m, streamTickMsgCmd()
|
|
||||||
default:
|
|
||||||
return m, streamTickMsgCmd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m, tea.Batch(cmds...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m model) View() string {
|
|
||||||
header := headerStyle.Render("=== Grokkit TUI Chat === q/Ctrl+C to quit")
|
|
||||||
vpView := m.viewport.View()
|
|
||||||
spinnerLine := ""
|
|
||||||
if m.streaming {
|
|
||||||
spinnerLine = m.spinner.View() + " Grok typing..."
|
|
||||||
}
|
|
||||||
inputLine := m.input.View()
|
|
||||||
return lipgloss.JoinVertical(lipgloss.Left, header, vpView, spinnerLine, inputLine)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m model) Init() tea.Cmd {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var chatCmd = &cobra.Command{
|
var chatCmd = &cobra.Command{
|
||||||
Use: "chat",
|
Use: "chat",
|
||||||
Short: "Interactive TUI chat with Grok",
|
Short: "Interactive streaming chat with Grok",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
p := tea.NewProgram(initialModel(), tea.WithAltScreen(), tea.WithMouseCellMotion())
|
client := grok.NewClient()
|
||||||
if _, err := p.Run(); err != nil {
|
color.Cyan("Grokkit Chat — type /quit or Ctrl+C to exit\n")
|
||||||
color.Red("Chat failed: %v", err)
|
|
||||||
os.Exit(1)
|
history := []map[string]string{}
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
|
||||||
|
for {
|
||||||
|
fmt.Print(color.YellowString("You: "))
|
||||||
|
if !scanner.Scan() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
input := strings.TrimSpace(scanner.Text())
|
||||||
|
if input == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if input == "/quit" || input == "/q" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
history = append(history, map[string]string{"role": "user", "content": input})
|
||||||
|
color.Green("Grok: ")
|
||||||
|
reply := client.Stream(history, "grok-4")
|
||||||
|
history = append(history, map[string]string{"role": "assistant", "content": reply})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,13 +2,10 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
|
||||||
"gmgauthier.com/grokkit/internal/git"
|
"gmgauthier.com/grokkit/internal/git"
|
||||||
"gmgauthier.com/grokkit/internal/grok"
|
"gmgauthier.com/grokkit/internal/grok"
|
||||||
)
|
)
|
||||||
@ -17,63 +14,32 @@ var commitCmd = &cobra.Command{
|
|||||||
Use: "commit",
|
Use: "commit",
|
||||||
Short: "Generate message and commit staged changes",
|
Short: "Generate message and commit staged changes",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
model := viper.GetString("model")
|
diff := git.Run([]string{"diff", "--cached", "--no-color"})
|
||||||
|
if diff == "" {
|
||||||
client := grok.NewClient()
|
color.Yellow("No staged changes!")
|
||||||
|
|
||||||
if !git.IsRepo() {
|
|
||||||
color.Red("Error: Not inside a git repository")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
diffOutput := git.Run([]string{"diff", "--cached"})
|
|
||||||
|
|
||||||
if diffOutput == "" {
|
|
||||||
color.Yellow("No staged changes. Stage files with `git add` first.")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
client := grok.NewClient()
|
||||||
systemPrompt := `You are an expert at writing conventional commit messages.
|
|
||||||
|
|
||||||
Analyze the code diff below and generate a concise commit message:
|
|
||||||
|
|
||||||
- Prefix: feat|fix|docs|style|refactor|perf|test|ci|chore
|
|
||||||
- Imperative mood (e.g., 'Add', 'Fix'), capitalize first letter
|
|
||||||
- Max 72 chars for subject line
|
|
||||||
- Optional body lines wrapped at 72 chars
|
|
||||||
- No trailing period on subject`
|
|
||||||
|
|
||||||
messages := []map[string]string{
|
messages := []map[string]string{
|
||||||
{"role": "system", "content": systemPrompt},
|
{"role": "system", "content": "Return ONLY a conventional commit message (type(scope): subject\n\nbody)."},
|
||||||
{"role": "user", "content": diffOutput},
|
{"role": "user", "content": fmt.Sprintf("Staged changes:\n%s", diff)},
|
||||||
}
|
}
|
||||||
|
color.Yellow("Generating commit message...")
|
||||||
|
msg := client.Stream(messages, "grok-4")
|
||||||
|
|
||||||
commitMsg := client.Stream(messages, model)
|
color.Cyan("\nProposed commit message:\n%s", msg)
|
||||||
|
|
||||||
fmt.Println(color.YellowString("Suggested commit message:"))
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Print(color.CyanString(commitMsg))
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
fmt.Print(color.YellowString("Commit now? (y/N): "))
|
|
||||||
var confirm string
|
var confirm string
|
||||||
|
color.Yellow("Commit with this message? (y/n): ")
|
||||||
fmt.Scanln(&confirm)
|
fmt.Scanln(&confirm)
|
||||||
if strings.ToLower(strings.TrimSpace(confirm)) != "y" {
|
if confirm != "y" && confirm != "Y" {
|
||||||
color.Yellow("Aborted.")
|
color.Yellow("Aborted.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdOut, err := exec.Command("git", "commit", "-m", commitMsg).CombinedOutput()
|
if err := exec.Command("git", "commit", "-m", msg).Run(); err != nil {
|
||||||
if err != nil {
|
color.Red("Git commit failed")
|
||||||
color.Red("Commit failed: %%v\\nOutput: %%s", err, cmdOut)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
outputStr := strings.TrimSpace(string(cmdOut))
|
|
||||||
if len(outputStr) > 0 {
|
|
||||||
fmt.Println(color.GreenString("Commit output: ") + outputStr)
|
|
||||||
} else {
|
} else {
|
||||||
color.Green("Committed successfully!")
|
color.Green("✅ Committed successfully!")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,9 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
|
||||||
"gmgauthier.com/grokkit/internal/git"
|
"gmgauthier.com/grokkit/internal/git"
|
||||||
"gmgauthier.com/grokkit/internal/grok"
|
"gmgauthier.com/grokkit/internal/grok"
|
||||||
)
|
)
|
||||||
@ -15,42 +13,17 @@ var commitMsgCmd = &cobra.Command{
|
|||||||
Use: "commit-msg",
|
Use: "commit-msg",
|
||||||
Short: "Generate conventional commit message from staged changes",
|
Short: "Generate conventional commit message from staged changes",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
model := viper.GetString("model")
|
diff := git.Run([]string{"diff", "--cached", "--no-color"})
|
||||||
|
if diff == "" {
|
||||||
|
color.Yellow("No staged changes!")
|
||||||
|
return
|
||||||
|
}
|
||||||
client := grok.NewClient()
|
client := grok.NewClient()
|
||||||
|
|
||||||
if !git.IsRepo() {
|
|
||||||
color.Red("Error: Not inside a git repository")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
diffOutput := git.Run([]string{"diff", "--cached"})
|
|
||||||
|
|
||||||
if diffOutput == "" {
|
|
||||||
color.Red("Error: No staged changes. Stage files with `git add` first.")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
systemPrompt := `You are an expert at writing conventional commit messages.
|
|
||||||
|
|
||||||
Analyze the code diff below and generate a concise commit message:
|
|
||||||
|
|
||||||
- Prefix: feat|fix|docs|style|refactor|perf|test|ci|chore
|
|
||||||
- Imperative mood (e.g., 'Add', 'Fix'), capitalize first letter
|
|
||||||
- Max 72 chars for subject line
|
|
||||||
- Optional body lines wrapped at 72 chars
|
|
||||||
- No trailing period on subject`
|
|
||||||
|
|
||||||
messages := []map[string]string{
|
messages := []map[string]string{
|
||||||
{"role": "system", "content": systemPrompt},
|
{"role": "system", "content": "Return ONLY a conventional commit message (type(scope): subject\n\nbody)."},
|
||||||
{"role": "user", "content": diffOutput},
|
{"role": "user", "content": fmt.Sprintf("Staged changes:\n%s", diff)},
|
||||||
}
|
}
|
||||||
|
color.Yellow("Generating commit message...")
|
||||||
commitMsg := client.Stream(messages, model)
|
client.Stream(messages, "grok-4")
|
||||||
|
|
||||||
fmt.Println(color.YellowString("Suggested commit message:"))
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Print(color.CyanString(commitMsg))
|
|
||||||
fmt.Println()
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
112
cmd/edit.go
112
cmd/edit.go
@ -4,10 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
"gmgauthier.com/grokkit/internal/git"
|
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -16,114 +12,42 @@ import (
|
|||||||
|
|
||||||
var editCmd = &cobra.Command{
|
var editCmd = &cobra.Command{
|
||||||
Use: "edit FILE INSTRUCTION",
|
Use: "edit FILE INSTRUCTION",
|
||||||
Short: "Edit a file in-place with Grok",
|
Short: "Edit a file in-place with Grok (safe preview + backup)",
|
||||||
|
Args: cobra.ExactArgs(2),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if len(args) < 2 {
|
|
||||||
color.Red("Usage: grokkit edit <file> <instruction>")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath := args[0]
|
filePath := args[0]
|
||||||
instruction := strings.Join(args[1:], " ")
|
instruction := args[1]
|
||||||
|
|
||||||
content, err := os.ReadFile(filePath)
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||||
if err != nil {
|
color.Red("File not found: %s", filePath)
|
||||||
color.Red("Error reading file %s: %v", filePath, err)
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
original, _ := os.ReadFile(filePath)
|
||||||
backupPath := filePath + ".bak"
|
backupPath := filePath + ".bak"
|
||||||
if backupErr := os.WriteFile(backupPath, content, 0o644); backupErr != nil {
|
_ = os.WriteFile(backupPath, original, 0644)
|
||||||
color.Yellow("Warning: backup failed %s: %v", backupPath, backupErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
model := viper.GetString("model")
|
|
||||||
client := grok.NewClient()
|
client := grok.NewClient()
|
||||||
|
|
||||||
systemPrompt := `You are an expert software engineer.
|
|
||||||
|
|
||||||
Your task is to edit code files based on instructions.
|
|
||||||
|
|
||||||
IMPORTANT: Respond ONLY with the COMPLETE new file content.
|
|
||||||
No explanations, no markdown, no comments outside the code.`
|
|
||||||
|
|
||||||
userPrompt := fmt.Sprintf(`File: %s
|
|
||||||
Current content:
|
|
||||||
%s
|
|
||||||
|
|
||||||
Instruction: %s
|
|
||||||
|
|
||||||
Output ONLY the full updated file content:`, filePath, content, instruction)
|
|
||||||
|
|
||||||
messages := []map[string]string{
|
messages := []map[string]string{
|
||||||
{"role": "system", "content": systemPrompt},
|
{"role": "system", "content": "Return ONLY the complete updated file content. No explanations, no markdown, no diffs."},
|
||||||
{"role": "user", "content": userPrompt},
|
{"role": "user", "content": fmt.Sprintf("File: %s\n\nOriginal content:\n%s\n\nTask: %s", filepath.Base(filePath), original, instruction)},
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanCodeResponse := func(s string) string {
|
color.Yellow("Asking Grok to %s...", instruction)
|
||||||
lines := strings.Split(s, "\n")
|
newContent := client.Stream(messages, "grok-4-1-fast-non-reasoning")
|
||||||
if len(lines) > 0 {
|
|
||||||
first := strings.TrimSpace(lines[0])
|
|
||||||
if strings.HasPrefix(first, "```") {
|
|
||||||
lines = lines[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(lines) > 0 {
|
|
||||||
last := strings.TrimSpace(lines[len(lines)-1])
|
|
||||||
if strings.HasSuffix(last, "```") {
|
|
||||||
lines = lines[:len(lines)-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(strings.Join(lines, "\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
newContent := cleanCodeResponse(client.Stream(messages, model))
|
color.Cyan("\nProposed changes (new file content):")
|
||||||
|
color.White(newContent)
|
||||||
|
|
||||||
dir := filepath.Dir(filePath)
|
fmt.Print("\nApply these changes? (y/n): ")
|
||||||
base := filepath.Base(filePath)
|
|
||||||
tmpA, err := os.CreateTemp(dir, "grokkit."+base+".old~")
|
|
||||||
if err != nil {
|
|
||||||
color.Red("Temp error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Remove(tmpA.Name())
|
|
||||||
tmpA.Write(content)
|
|
||||||
tmpA.Close()
|
|
||||||
|
|
||||||
tmpB, err := os.CreateTemp(dir, "grokkit."+base+".new~")
|
|
||||||
if err != nil {
|
|
||||||
color.Red("Temp error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Remove(tmpB.Name())
|
|
||||||
tmpB.Write([]byte(newContent))
|
|
||||||
tmpB.Close()
|
|
||||||
|
|
||||||
diffOut := git.Run([]string{"diff", "--no-index", "--no-color", tmpA.Name(), tmpB.Name()})
|
|
||||||
|
|
||||||
fmt.Println(color.YellowString("📄 Backup: %s", backupPath))
|
|
||||||
fmt.Println()
|
|
||||||
if diffOut != "" {
|
|
||||||
fmt.Println(color.YellowString("🔄 Proposed changes:"))
|
|
||||||
fmt.Print(color.CyanString(diffOut))
|
|
||||||
} else {
|
|
||||||
color.Yellow("No changes.")
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
fmt.Print(color.YellowString("Apply? (y/N): "))
|
|
||||||
var confirm string
|
var confirm string
|
||||||
fmt.Scanln(&confirm)
|
fmt.Scanln(&confirm)
|
||||||
if strings.ToLower(strings.TrimSpace(confirm)) != "y" {
|
if confirm != "y" && confirm != "Y" {
|
||||||
color.Yellow("Aborted.")
|
color.Yellow("Changes discarded. Backup saved as %s", backupPath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(filePath, []byte(newContent), 0644); err != nil {
|
_ = os.WriteFile(filePath, []byte(newContent), 0644)
|
||||||
color.Red("Error writing file: %v", err)
|
color.Green("✅ Applied successfully! Backup: %s", backupPath)
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
color.Green("File %s updated successfully!", filePath)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,8 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
|
||||||
"gmgauthier.com/grokkit/internal/git"
|
"gmgauthier.com/grokkit/internal/git"
|
||||||
"gmgauthier.com/grokkit/internal/grok"
|
"gmgauthier.com/grokkit/internal/grok"
|
||||||
)
|
)
|
||||||
@ -15,42 +11,17 @@ var historyCmd = &cobra.Command{
|
|||||||
Use: "history",
|
Use: "history",
|
||||||
Short: "Summarize recent git history",
|
Short: "Summarize recent git history",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
model := viper.GetString("model")
|
log := git.Run([]string{"log", "--oneline", "-10"})
|
||||||
|
if log == "" {
|
||||||
|
color.Yellow("No commits found.")
|
||||||
|
return
|
||||||
|
}
|
||||||
client := grok.NewClient()
|
client := grok.NewClient()
|
||||||
|
|
||||||
if !git.IsRepo() {
|
|
||||||
color.Red("Error: Not inside a git repository")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
logOutput := git.Run([]string{"log", "--oneline", "-10"})
|
|
||||||
|
|
||||||
if logOutput == "" {
|
|
||||||
color.Red("Error: No git history found.")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
systemPrompt := `You are a git workflow expert.
|
|
||||||
|
|
||||||
Summarize the recent git history:
|
|
||||||
|
|
||||||
- Key changes and themes
|
|
||||||
- Potential issues or todos
|
|
||||||
- Suggestions for next steps or refactoring
|
|
||||||
|
|
||||||
Format as bullet points.`
|
|
||||||
|
|
||||||
messages := []map[string]string{
|
messages := []map[string]string{
|
||||||
{"role": "system", "content": systemPrompt},
|
{"role": "system", "content": "Summarize the recent git history in 3-5 bullet points."},
|
||||||
{"role": "user", "content": logOutput},
|
{"role": "user", "content": log},
|
||||||
}
|
}
|
||||||
|
color.Yellow("Summarizing recent commits...")
|
||||||
summary := client.Stream(messages, model)
|
client.Stream(messages, "grok-4")
|
||||||
|
|
||||||
fmt.Println(color.YellowString("📜 Git History Summary:"))
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Print(color.CyanString(summary))
|
|
||||||
fmt.Println()
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
|
||||||
"gmgauthier.com/grokkit/internal/git"
|
"gmgauthier.com/grokkit/internal/git"
|
||||||
"gmgauthier.com/grokkit/internal/grok"
|
"gmgauthier.com/grokkit/internal/grok"
|
||||||
)
|
)
|
||||||
@ -14,39 +13,20 @@ var prDescribeCmd = &cobra.Command{
|
|||||||
Use: "pr-describe",
|
Use: "pr-describe",
|
||||||
Short: "Generate full PR description from current branch",
|
Short: "Generate full PR description from current branch",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
model := viper.GetString("model")
|
diff := git.Run([]string{"diff", "main..HEAD", "--no-color"})
|
||||||
|
if diff == "" {
|
||||||
client := grok.NewClient()
|
diff = git.Run([]string{"diff", "origin/main..HEAD", "--no-color"})
|
||||||
|
|
||||||
if !git.IsRepo() {
|
|
||||||
color.Yellow("Not a git repo.")
|
|
||||||
}
|
}
|
||||||
|
if diff == "" {
|
||||||
logMain := git.Run([]string{"log", "--oneline", "main..HEAD"})
|
color.Yellow("No changes on this branch compared to main/origin/main.")
|
||||||
if logMain == "" {
|
|
||||||
logMain = git.Run([]string{"log", "--oneline", "origin/main..HEAD"})
|
|
||||||
}
|
|
||||||
diffMain := git.Run([]string{"diff", "main..HEAD"})
|
|
||||||
if diffMain == "" {
|
|
||||||
diffMain = git.Run([]string{"diff", "origin/main..HEAD"})
|
|
||||||
}
|
|
||||||
if logMain == "" && diffMain == "" {
|
|
||||||
color.Yellow("No changes vs main/origin/main. Nothing to describe.")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
client := grok.NewClient()
|
||||||
systemPrompt := `Write a professional GitHub PR title + detailed body (changes, motivation, testing notes).`
|
|
||||||
|
|
||||||
messages := []map[string]string{
|
messages := []map[string]string{
|
||||||
{"role": "system", "content": systemPrompt},
|
{"role": "system", "content": "Write a professional GitHub PR title + detailed body (changes, motivation, testing notes)."},
|
||||||
{"role": "user", "content": "Branch changes:\n" + logMain + "\n\nDiff:\n" + diffMain},
|
{"role": "user", "content": fmt.Sprintf("Diff:\n%s", diff)},
|
||||||
}
|
}
|
||||||
|
color.Yellow("Writing PR description...")
|
||||||
prDesc := client.Stream(messages, model)
|
client.Stream(messages, "grok-4")
|
||||||
|
|
||||||
fmt.Println(color.YellowString("📝 Suggested PR Description:"))
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Print(color.CyanString(prDesc))
|
|
||||||
fmt.Println()
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
|
||||||
"gmgauthier.com/grokkit/internal/git"
|
"gmgauthier.com/grokkit/internal/git"
|
||||||
"gmgauthier.com/grokkit/internal/grok"
|
"gmgauthier.com/grokkit/internal/grok"
|
||||||
)
|
)
|
||||||
@ -14,35 +13,15 @@ var reviewCmd = &cobra.Command{
|
|||||||
Use: "review [path]",
|
Use: "review [path]",
|
||||||
Short: "Review the current repository or directory",
|
Short: "Review the current repository or directory",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
model := viper.GetString("model")
|
|
||||||
|
|
||||||
client := grok.NewClient()
|
client := grok.NewClient()
|
||||||
|
diff := git.Run([]string{"diff", "--no-color"})
|
||||||
if !git.IsRepo() {
|
|
||||||
color.Yellow("Not a git repo — basic analysis only.")
|
|
||||||
}
|
|
||||||
|
|
||||||
status := git.Run([]string{"status", "--short"})
|
status := git.Run([]string{"status", "--short"})
|
||||||
unstagedDiff := git.Run([]string{"diff"})
|
|
||||||
files := git.Run([]string{"ls-files"})
|
|
||||||
if files == "" {
|
|
||||||
files = "No tracked files."
|
|
||||||
}
|
|
||||||
context := fmt.Sprintf(`Git status:\n\n%s\n\nUnstaged diff:\n%s\n\nTracked files:\n%s`, status, unstagedDiff, files)
|
|
||||||
|
|
||||||
systemPrompt := `You are an expert code reviewer.
|
|
||||||
Give a concise summary + 3–5 actionable improvements.`
|
|
||||||
|
|
||||||
messages := []map[string]string{
|
messages := []map[string]string{
|
||||||
{"role": "system", "content": systemPrompt},
|
{"role": "system", "content": "You are an expert code reviewer. Give a concise summary + 3-5 actionable improvements."},
|
||||||
{"role": "user", "content": "Review this project:\\n" + context},
|
{"role": "user", "content": fmt.Sprintf("Git status:\n%s\n\nGit diff:\n%s", status, diff)},
|
||||||
}
|
}
|
||||||
|
color.Yellow("Grok is reviewing the repo...")
|
||||||
review := client.Stream(messages, model)
|
client.Stream(messages, "grok-4")
|
||||||
|
|
||||||
fmt.Println(color.YellowString("🤖 AI Code Review:"))
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Print(color.CyanString(review))
|
|
||||||
fmt.Println()
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
21
go.mod
21
go.mod
@ -3,45 +3,24 @@ module gmgauthier.com/grokkit
|
|||||||
go 1.24.2
|
go 1.24.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charmbracelet/bubbles v1.0.0
|
|
||||||
github.com/charmbracelet/bubbletea v1.3.10
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.0
|
|
||||||
github.com/fatih/color v1.18.0
|
github.com/fatih/color v1.18.0
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/spf13/viper v1.21.0
|
github.com/spf13/viper v1.21.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
|
||||||
github.com/charmbracelet/colorprofile v0.4.1 // indirect
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.6 // indirect
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
|
|
||||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
|
||||||
github.com/clipperhouse/displaywidth v0.9.0 // indirect
|
|
||||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
|
||||||
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
|
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
|
||||||
github.com/muesli/termenv v0.16.0 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
|
||||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
github.com/spf13/afero v1.15.0 // indirect
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
github.com/spf13/cast v1.10.0 // indirect
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.38.0 // indirect
|
||||||
golang.org/x/text v0.28.0 // indirect
|
golang.org/x/text v0.28.0 // indirect
|
||||||
|
|||||||
45
go.sum
45
go.sum
@ -1,32 +1,6 @@
|
|||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
|
||||||
github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
|
|
||||||
github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
|
|
||||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
|
||||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
|
||||||
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
|
|
||||||
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
|
|
||||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
|
||||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
|
||||||
github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
|
|
||||||
github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
|
|
||||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
|
||||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
|
||||||
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
|
|
||||||
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
@ -43,29 +17,15 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
|
||||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
|
||||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
|
||||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
|
||||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
|
||||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
|
||||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
@ -88,13 +48,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
|||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
|
|||||||
@ -1,13 +1,10 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import "os/exec"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Run(args []string) string {
|
func Run(args []string) string {
|
||||||
out, _ := exec.Command("git", args...).CombinedOutput()
|
out, _ := exec.Command("git", args...).Output()
|
||||||
return strings.TrimSpace(string(out))
|
return string(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsRepo() bool {
|
func IsRepo() bool {
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@ -49,31 +48,26 @@ func (c *Client) Stream(messages []map[string]string, model string) string {
|
|||||||
color.Red("Request failed: %v", err)
|
color.Red("Request failed: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if resp.StatusCode != http.StatusOK {
|
defer func() {
|
||||||
bodyBytes, readErr := io.ReadAll(resp.Body)
|
if cerr := resp.Body.Close(); cerr != nil {
|
||||||
if readErr != nil {
|
color.Yellow("Warning: failed to close response body: %v", cerr)
|
||||||
color.Red("Failed to read response body: %v", readErr)
|
|
||||||
}
|
}
|
||||||
color.Red("API failed with status %d: %s", resp.StatusCode, string(bodyBytes))
|
}()
|
||||||
resp.Body.Close()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var fullReply strings.Builder
|
var fullReply strings.Builder
|
||||||
scanner := bufio.NewScanner(resp.Body)
|
scanner := bufio.NewScanner(resp.Body)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
if strings.HasPrefix(line, "data: ") {
|
if strings.HasPrefix(line, "data: ") {
|
||||||
data := line[6:]
|
data := strings.TrimPrefix(line, "data: ")
|
||||||
if data == "[DONE]" {
|
if data == "[DONE]" {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
var chunk map[string]interface{}
|
var chunk map[string]any
|
||||||
if json.Unmarshal([]byte(data), &chunk) == nil {
|
if json.Unmarshal([]byte(data), &chunk) == nil {
|
||||||
if choices, ok := chunk["choices"].([]interface{}); ok && len(choices) > 0 {
|
if choices, ok := chunk["choices"].([]any); ok && len(choices) > 0 {
|
||||||
if delta, ok := choices[0].(map[string]interface{})["delta"].(map[string]interface{}); ok {
|
if delta, ok := choices[0].(map[string]any)["delta"].(map[string]any); ok {
|
||||||
if content, ok := delta["content"].(string); ok {
|
if content, ok := delta["content"].(string); ok && content != "" {
|
||||||
fmt.Print(content)
|
fmt.Print(content)
|
||||||
fullReply.WriteString(content)
|
fullReply.WriteString(content)
|
||||||
}
|
}
|
||||||
@ -85,54 +79,3 @@ func (c *Client) Stream(messages []map[string]string, model string) string {
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
return fullReply.String()
|
return fullReply.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) StreamChan(messages []map[string]string, model string) <-chan string {
|
|
||||||
ch := make(chan string, 100)
|
|
||||||
go func() {
|
|
||||||
defer close(ch)
|
|
||||||
url := c.BaseURL + "/chat/completions"
|
|
||||||
payload := map[string]interface{}{
|
|
||||||
"model": model,
|
|
||||||
"messages": messages,
|
|
||||||
"temperature": 0.7,
|
|
||||||
"stream": true,
|
|
||||||
}
|
|
||||||
body, _ := json.Marshal(payload)
|
|
||||||
req, _ := http.NewRequest("POST", url, bytes.NewReader(body))
|
|
||||||
req.Header.Set("Authorization", "Bearer "+c.APIKey)
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
color.Red("Request failed: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
|
||||||
color.Red("API failed with status %d: %s", resp.StatusCode, string(bodyBytes))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
scanner := bufio.NewScanner(resp.Body)
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
if strings.HasPrefix(line, "data: ") {
|
|
||||||
data := line[6:]
|
|
||||||
if data == "[DONE]" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
var chunk map[string]interface{}
|
|
||||||
if json.Unmarshal([]byte(data), &chunk) != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if choices, ok := chunk["choices"].([]interface{}); ok && len(choices) > 0 {
|
|
||||||
if delta, ok := choices[0].(map[string]interface{})["delta"].(map[string]interface{}); ok {
|
|
||||||
if content, ok := delta["content"].(string); ok && content != "" {
|
|
||||||
ch <- content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user