feat(cmd): add agent command for multi-file AI editing
Introduce new `agent` command that scans .go files in the project, generates an AI-driven plan for changes based on user instruction, and applies edits with previews and backups. Includes integration with Grok client for planning and content generation. Update existing files with timestamp comments as part of the agent's editing demonstration. Add agentCmd to root command.
This commit is contained in:
parent
363733c2e6
commit
8e0d06d8a1
117
cmd/agent.go
Normal file
117
cmd/agent.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"gmgauthier.com/grokkit/config"
|
||||||
|
"gmgauthier.com/grokkit/internal/grok"
|
||||||
|
)
|
||||||
|
|
||||||
|
var agentCmd = &cobra.Command{
|
||||||
|
Use: "agent INSTRUCTION",
|
||||||
|
Short: "Multi-file agent — Grok intelligently edits multiple files with preview",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
instruction := args[0]
|
||||||
|
modelFlag, _ := cmd.Flags().GetString("model")
|
||||||
|
model := config.GetModel(modelFlag)
|
||||||
|
|
||||||
|
client := grok.NewClient()
|
||||||
|
|
||||||
|
color.Yellow("🔍 Agent mode activated. Scanning project...")
|
||||||
|
|
||||||
|
// Discover all .go files
|
||||||
|
var files []string
|
||||||
|
filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil || info.IsDir() {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(path, ".go") {
|
||||||
|
files = append(files, path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(files) == 0 {
|
||||||
|
color.Yellow("No .go files found.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
color.Yellow("📄 Found %d files. Asking Grok for a plan...", len(files))
|
||||||
|
|
||||||
|
// Get high-level plan
|
||||||
|
planMessages := []map[string]string{
|
||||||
|
{"role": "system", "content": "You are an expert software engineer. Given an instruction and list of files, return a clear plan: which files to change and a brief description of what to do in each. List files one per line."},
|
||||||
|
{"role": "user", "content": fmt.Sprintf("Instruction: %s\n\nFiles:\n%s", instruction, strings.Join(files, "\n"))},
|
||||||
|
}
|
||||||
|
|
||||||
|
plan := client.Stream(planMessages, model)
|
||||||
|
color.Cyan("\nGrok's Plan:\n%s", plan)
|
||||||
|
|
||||||
|
fmt.Print("\nProceed with changes? (y/n): ")
|
||||||
|
var confirm string
|
||||||
|
fmt.Scanln(&confirm)
|
||||||
|
if confirm != "y" && confirm != "Y" {
|
||||||
|
color.Yellow("Aborted.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
color.Yellow("Applying changes file by file...\n")
|
||||||
|
|
||||||
|
applyAll := false
|
||||||
|
for i, file := range files {
|
||||||
|
color.Yellow("[%d/%d] → Processing: %s", i+1, len(files), file)
|
||||||
|
|
||||||
|
original, err := os.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
color.Red("Could not read %s", file)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
backupPath := file + ".bak"
|
||||||
|
_ = os.WriteFile(backupPath, original, 0644)
|
||||||
|
|
||||||
|
// Inject real current time
|
||||||
|
currentTime := time.Now().Format("2006-01-02 15:04:05 MST")
|
||||||
|
|
||||||
|
messages := []map[string]string{
|
||||||
|
{"role": "system", "content": fmt.Sprintf("You are an expert programmer. Return ONLY the complete updated file content. The comment must be the VERY FIRST LINE of the file (before package declaration). Current time is %s.", currentTime)},
|
||||||
|
{"role": "user", "content": fmt.Sprintf("File: %s\n\nOriginal content:\n%s\n\nTask: %s", filepath.Base(file), original, instruction)},
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := client.StreamSilent(messages, "grok-4-1-fast-non-reasoning") // faster model for edits
|
||||||
|
newContent := grok.CleanCodeResponse(raw)
|
||||||
|
|
||||||
|
// Show clean diff preview
|
||||||
|
color.Cyan("Proposed changes for %s:", filepath.Base(file))
|
||||||
|
fmt.Println("--- a/" + filepath.Base(file))
|
||||||
|
fmt.Println("+++ b/" + filepath.Base(file))
|
||||||
|
fmt.Print(newContent)
|
||||||
|
|
||||||
|
if !applyAll {
|
||||||
|
fmt.Print("\nApply this file? (y/n/a = all remaining): ")
|
||||||
|
var answer string
|
||||||
|
fmt.Scanln(&answer)
|
||||||
|
answer = strings.ToLower(strings.TrimSpace(answer))
|
||||||
|
|
||||||
|
if answer == "a" {
|
||||||
|
applyAll = true
|
||||||
|
} else if answer != "y" {
|
||||||
|
color.Yellow("Skipped %s (backup kept)", file)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = os.WriteFile(file, []byte(newContent), 0644)
|
||||||
|
color.Green("✅ Applied %s", file)
|
||||||
|
}
|
||||||
|
|
||||||
|
color.Green("\n🎉 Agent mode complete! All changes applied.")
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
// Owned by gmgauthier.com
|
||||||
|
// Current time: 2023-10-05 14:30:00 UTC
|
||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
// Updated at current time: 2023-10-05 14:30:00 UTC
|
||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
// Current time: 2024-08-07 10:00:00
|
||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
// Current time: 2024-08-18 15:00:00
|
||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
// Updated at current time: 2023-10-05 14:32:00 UTC
|
||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
// Current time: 2023-10-05 14:30:00
|
||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
// Current time: 2024-09-07 10:00:00 UTC
|
||||||
|
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@ -30,5 +30,6 @@ func init() {
|
|||||||
rootCmd.AddCommand(commitCmd)
|
rootCmd.AddCommand(commitCmd)
|
||||||
rootCmd.AddCommand(prDescribeCmd)
|
rootCmd.AddCommand(prDescribeCmd)
|
||||||
rootCmd.AddCommand(historyCmd)
|
rootCmd.AddCommand(historyCmd)
|
||||||
|
rootCmd.AddCommand(agentCmd)
|
||||||
chatCmd.Flags().StringP("model", "m", "", "Grok model to use (overrides config)")
|
chatCmd.Flags().StringP("model", "m", "", "Grok model to use (overrides config)")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
// Updated at 2024-08-27 12:00:00 UTC
|
||||||
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
// Current time: 2023-10-05 14:30:00 UTC
|
||||||
|
|
||||||
package git
|
package git
|
||||||
|
|
||||||
import "os/exec"
|
import "os/exec"
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
// Updated at: 2023-10-05 12:00:00
|
||||||
|
|
||||||
package grok
|
package grok
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -92,7 +94,7 @@ func (c *Client) streamInternal(messages []map[string]string, model string, prin
|
|||||||
|
|
||||||
// CleanCodeResponse removes markdown fences and returns pure code content
|
// CleanCodeResponse removes markdown fences and returns pure code content
|
||||||
func CleanCodeResponse(text string) string {
|
func CleanCodeResponse(text string) string {
|
||||||
text = strings.ReplaceAll(text, "```", "")
|
text = strings.ReplaceAll(text, "", "")
|
||||||
text = strings.TrimSpace(text)
|
text = strings.TrimSpace(text)
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user