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:
Greg Gauthier 2026-02-28 22:29:16 +00:00
parent 363733c2e6
commit 8e0d06d8a1
13 changed files with 153 additions and 12 deletions

117
cmd/agent.go Normal file
View 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.")
},
}

View File

@ -1,3 +1,6 @@
// Owned by gmgauthier.com
// Current time: 2023-10-05 14:30:00 UTC
package cmd
import (

View File

@ -1,3 +1,5 @@
// Updated at current time: 2023-10-05 14:30:00 UTC
package cmd
import (

View File

@ -1,3 +1,5 @@
// Current time: 2024-08-07 10:00:00
package cmd
import (

View File

@ -1,3 +1,5 @@
// Current time: 2024-08-18 15:00:00
package cmd
import (

View File

@ -1,3 +1,5 @@
// Updated at current time: 2023-10-05 14:32:00 UTC
package cmd
import (

View File

@ -1,3 +1,5 @@
// Current time: 2023-10-05 14:30:00
package cmd
import (

View File

@ -1,3 +1,5 @@
// Current time: 2024-09-07 10:00:00 UTC
package cmd
import (

View File

@ -30,5 +30,6 @@ func init() {
rootCmd.AddCommand(commitCmd)
rootCmd.AddCommand(prDescribeCmd)
rootCmd.AddCommand(historyCmd)
rootCmd.AddCommand(agentCmd)
chatCmd.Flags().StringP("model", "m", "", "Grok model to use (overrides config)")
}

View File

@ -1,3 +1,5 @@
// Updated at 2024-08-27 12:00:00 UTC
package config
import (

View File

@ -1,3 +1,5 @@
// Current time: 2023-10-05 14:30:00 UTC
package git
import "os/exec"

View File

@ -1,3 +1,5 @@
// Updated at: 2023-10-05 12:00:00
package grok
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
func CleanCodeResponse(text string) string {
text = strings.ReplaceAll(text, "```", "")
text = strings.ReplaceAll(text, "", "")
text = strings.TrimSpace(text)
return text
}

View File

@ -1,3 +1,5 @@
// Updated at current time: 2023-10-05 12:00:00 UTC
package main
import "gmgauthier.com/grokkit/cmd"