2026-02-28 18:03:12 +00:00
package cmd
2026-02-28 18:28:27 +00:00
2026-02-28 18:41:20 +00:00
import (
2026-02-28 23:03:53 +00:00
"bufio"
2026-03-04 11:15:54 +00:00
"encoding/json"
2026-02-28 20:17:12 +00:00
"fmt"
2026-02-28 19:56:23 +00:00
"os"
2026-03-04 11:15:54 +00:00
"path/filepath"
2026-02-28 19:56:23 +00:00
"strings"
"github.com/fatih/color"
2026-02-28 18:41:20 +00:00
"github.com/spf13/cobra"
2026-03-04 11:15:54 +00:00
"github.com/spf13/viper"
2026-02-28 20:52:03 +00:00
"gmgauthier.com/grokkit/config"
2026-03-04 11:59:23 +00:00
"gmgauthier.com/grokkit/internal/git"
2026-03-04 11:04:08 +00:00
"gmgauthier.com/grokkit/internal/grok"
2026-02-28 18:41:20 +00:00
)
2026-02-28 18:28:27 +00:00
2026-03-04 11:15:54 +00:00
type ChatHistory struct {
Messages [ ] map [ string ] string ` json:"messages" `
}
2026-03-04 11:59:23 +00:00
type ToolCall struct {
Tool string ` json:"tool" `
File string ` json:"file,omitempty" `
Path string ` json:"path,omitempty" `
Instruction string ` json:"instruction,omitempty" `
Description string ` json:"description,omitempty" `
Message string ` json:"message,omitempty" `
}
2026-03-04 11:15:54 +00:00
func loadChatHistory ( ) [ ] map [ string ] string {
histFile := getChatHistoryFile ( )
data , err := os . ReadFile ( histFile )
if err != nil {
return nil
}
var hist ChatHistory
if err := json . Unmarshal ( data , & hist ) ; err != nil {
return nil
}
return hist . Messages
}
func saveChatHistory ( messages [ ] map [ string ] string ) error {
histFile := getChatHistoryFile ( )
hist := ChatHistory { Messages : messages }
data , err := json . MarshalIndent ( hist , "" , " " )
if err != nil {
return err
}
return os . WriteFile ( histFile , data , 0644 )
}
func getChatHistoryFile ( ) string {
configFile := viper . GetString ( "chat.history_file" )
if configFile != "" {
return configFile
}
home , _ := os . UserHomeDir ( )
if home == "" {
home = "."
}
histDir := filepath . Join ( home , ".config" , "grokkit" )
_ = os . MkdirAll ( histDir , 0755 )
return filepath . Join ( histDir , "chat_history.json" )
}
2026-03-04 10:49:53 +00:00
var chatCmd = & cobra . Command {
Use : "chat" ,
Short : "Interactive chat with Grok (use --agent for tool-enabled mode)" ,
Long : ` Start a persistent conversation with Grok .
feat: add CI/CD workflows, persistent chat, shell completions, and testing
- Add Gitea CI workflow for testing, linting, and building
- Add release workflow for multi-platform builds and GitHub releases
- Implement persistent chat history with JSON storage
- Add shell completion generation for bash, zsh, fish, powershell
- Introduce custom error types and logging system
- Add interfaces for git and AI client for better testability
- Enhance config with temperature and timeout settings
- Add comprehensive unit tests for config, errors, git, grok, and logger
- Update README with installation, features, and development instructions
- Make model flag persistent across commands
- Add context timeouts to API requests
2026-03-01 12:17:22 +00:00
2026-03-04 11:04:08 +00:00
Normal mode : coherent , reasoning - focused chat ( uses full model ) .
Agent mode ( -- agent ) : Grok can call tools with safety previews ( uses fast model ) . ` ,
2026-03-04 10:49:53 +00:00
Run : runChat ,
feat: add CI/CD workflows, persistent chat, shell completions, and testing
- Add Gitea CI workflow for testing, linting, and building
- Add release workflow for multi-platform builds and GitHub releases
- Implement persistent chat history with JSON storage
- Add shell completion generation for bash, zsh, fish, powershell
- Introduce custom error types and logging system
- Add interfaces for git and AI client for better testability
- Enhance config with temperature and timeout settings
- Add comprehensive unit tests for config, errors, git, grok, and logger
- Update README with installation, features, and development instructions
- Make model flag persistent across commands
- Add context timeouts to API requests
2026-03-01 12:17:22 +00:00
}
2026-03-04 10:49:53 +00:00
func init ( ) {
2026-03-04 11:04:08 +00:00
chatCmd . Flags ( ) . Bool ( "agent" , false , "Enable agent mode with tool calling (uses fast non-reasoning model)" )
chatCmd . Flags ( ) . String ( "model" , "" , "Override model" )
2026-03-04 10:49:53 +00:00
rootCmd . AddCommand ( chatCmd )
feat: add CI/CD workflows, persistent chat, shell completions, and testing
- Add Gitea CI workflow for testing, linting, and building
- Add release workflow for multi-platform builds and GitHub releases
- Implement persistent chat history with JSON storage
- Add shell completion generation for bash, zsh, fish, powershell
- Introduce custom error types and logging system
- Add interfaces for git and AI client for better testability
- Enhance config with temperature and timeout settings
- Add comprehensive unit tests for config, errors, git, grok, and logger
- Update README with installation, features, and development instructions
- Make model flag persistent across commands
- Add context timeouts to API requests
2026-03-01 12:17:22 +00:00
}
2026-03-04 10:49:53 +00:00
func runChat ( cmd * cobra . Command , args [ ] string ) {
agentMode , _ := cmd . Flags ( ) . GetBool ( "agent" )
2026-03-04 11:04:08 +00:00
modelFlag , _ := cmd . Flags ( ) . GetString ( "model" )
2026-03-04 10:49:53 +00:00
var model string
2026-03-04 11:04:08 +00:00
if modelFlag != "" {
model = modelFlag
2026-03-04 10:49:53 +00:00
} else if agentMode {
2026-03-04 11:15:54 +00:00
model = config . GetModel ( "chat-agent" , "" )
2026-03-04 10:49:53 +00:00
} else {
model = config . GetModel ( "chat" , "" )
feat: add CI/CD workflows, persistent chat, shell completions, and testing
- Add Gitea CI workflow for testing, linting, and building
- Add release workflow for multi-platform builds and GitHub releases
- Implement persistent chat history with JSON storage
- Add shell completion generation for bash, zsh, fish, powershell
- Introduce custom error types and logging system
- Add interfaces for git and AI client for better testability
- Enhance config with temperature and timeout settings
- Add comprehensive unit tests for config, errors, git, grok, and logger
- Update README with installation, features, and development instructions
- Make model flag persistent across commands
- Add context timeouts to API requests
2026-03-01 12:17:22 +00:00
}
2026-03-04 11:04:08 +00:00
client := grok . NewClient ( )
feat: add CI/CD workflows, persistent chat, shell completions, and testing
- Add Gitea CI workflow for testing, linting, and building
- Add release workflow for multi-platform builds and GitHub releases
- Implement persistent chat history with JSON storage
- Add shell completion generation for bash, zsh, fish, powershell
- Introduce custom error types and logging system
- Add interfaces for git and AI client for better testability
- Enhance config with temperature and timeout settings
- Add comprehensive unit tests for config, errors, git, grok, and logger
- Update README with installation, features, and development instructions
- Make model flag persistent across commands
- Add context timeouts to API requests
2026-03-01 12:17:22 +00:00
2026-03-04 11:04:08 +00:00
systemPrompt := map [ string ] string {
"role" : "system" ,
"content" : fmt . Sprintf ( "You are Grok 4, the latest and most powerful model from xAI (2026). You are currently running as `%s`. Be helpful, truthful, and a little irreverent." , model ) ,
}
2026-02-28 21:53:35 +00:00
2026-03-04 10:49:53 +00:00
if agentMode {
2026-03-04 11:50:21 +00:00
systemPrompt [ "content" ] = "You are Grok in Agent Mode.\n" +
"You can call tools using this exact JSON format inside ```tool blocks:\n\n" +
"```tool\n" +
"{\"tool\": \"edit\", \"file\": \"main.go\", \"instruction\": \"Add error handling\"}\n" +
"```\n\n" +
"Available tools: edit, scaffold, testgen, lint, commit.\n\n" +
"Always use tools when the user asks you to change code, generate tests, lint, or commit.\n" +
"After every tool call, wait for the result before continuing.\n" +
"Be concise and action-oriented."
2026-03-04 10:49:53 +00:00
}
2026-02-28 21:53:35 +00:00
2026-03-04 11:04:08 +00:00
history := loadChatHistory ( )
2026-03-04 11:19:50 +00:00
if len ( history ) == 0 {
2026-03-04 11:04:08 +00:00
history = [ ] map [ string ] string { systemPrompt }
} else if history [ 0 ] [ "role" ] != "system" {
history = append ( [ ] map [ string ] string { systemPrompt } , history ... )
} else {
2026-03-04 11:59:23 +00:00
history [ 0 ] = systemPrompt
2026-03-04 11:04:08 +00:00
}
color . Cyan ( "┌──────────────────────────────────────────────────────────────┐" )
color . Cyan ( "│ Grokkit Chat %s — Model: %s │" , map [ bool ] string { true : "(Agent Mode)" , false : "" } [ agentMode ] , model )
color . Cyan ( "│ Type /quit to exit │" )
color . Cyan ( "└──────────────────────────────────────────────────────────────┘\n" )
2026-02-28 21:53:35 +00:00
2026-03-04 10:49:53 +00:00
scanner := bufio . NewScanner ( os . Stdin )
for {
2026-03-04 11:04:08 +00:00
color . Yellow ( "You > " )
2026-03-04 10:49:53 +00:00
if ! scanner . Scan ( ) {
break
}
2026-02-28 21:53:35 +00:00
2026-03-04 11:04:08 +00:00
input := strings . TrimSpace ( scanner . Text ( ) )
2026-03-04 10:49:53 +00:00
if input == "" {
continue
}
2026-03-04 11:04:08 +00:00
if input == "/quit" || input == "/q" || input == "exit" {
color . Cyan ( "\nGoodbye 👋\n" )
break
}
2026-02-28 21:53:35 +00:00
2026-03-04 10:49:53 +00:00
history = append ( history , map [ string ] string { "role" : "user" , "content" : input } )
2026-02-28 19:56:23 +00:00
2026-03-04 11:04:08 +00:00
color . Green ( "Grok > " )
2026-03-04 10:49:53 +00:00
reply := client . Stream ( history , model )
2026-02-28 21:53:35 +00:00
2026-03-04 11:50:21 +00:00
if agentMode && strings . Contains ( reply , "```tool" ) {
2026-03-04 12:05:01 +00:00
handleToolCall ( reply , & history )
2026-03-04 11:50:21 +00:00
continue
}
history = append ( history , map [ string ] string { "role" : "assistant" , "content" : reply } )
_ = saveChatHistory ( history )
2026-03-04 11:04:08 +00:00
fmt . Println ( )
}
2026-02-28 22:59:16 +00:00
}
2026-03-04 11:59:23 +00:00
2026-03-04 12:05:01 +00:00
func handleToolCall ( reply string , history * [ ] map [ string ] string ) {
2026-03-04 11:59:23 +00:00
start := strings . Index ( reply , "```tool" )
if start == - 1 {
return
}
end := strings . Index ( reply [ start + 7 : ] , "```" )
if end == - 1 {
return
}
block := strings . TrimSpace ( reply [ start + 7 : start + 7 + end ] )
var tc ToolCall
if err := json . Unmarshal ( [ ] byte ( block ) , & tc ) ; err != nil {
color . Red ( "Failed to parse tool call: %v" , err )
return
}
color . Yellow ( "\n[Agent] Grok wants to call tool: %s" , tc . Tool )
switch tc . Tool {
case "edit" :
2026-03-04 12:24:23 +00:00
if tc . File != "" {
2026-03-04 12:08:51 +00:00
editCmd . SetArgs ( [ ] string { tc . File } )
2026-03-04 12:05:01 +00:00
_ = editCmd . Execute ( )
2026-03-04 11:59:23 +00:00
}
case "scaffold" :
2026-03-04 12:24:23 +00:00
if tc . Path != "" {
2026-03-04 12:08:51 +00:00
scaffoldCmd . SetArgs ( [ ] string { tc . Path } )
2026-03-04 12:05:01 +00:00
_ = scaffoldCmd . Execute ( )
2026-03-04 11:59:23 +00:00
}
case "testgen" :
if tc . File != "" {
2026-03-04 12:08:51 +00:00
testgenCmd . SetArgs ( [ ] string { tc . File } )
2026-03-04 12:05:01 +00:00
_ = testgenCmd . Execute ( )
2026-03-04 11:59:23 +00:00
}
case "lint" :
if tc . File != "" {
2026-03-04 12:08:51 +00:00
lintCmd . SetArgs ( [ ] string { tc . File } )
2026-03-04 12:05:01 +00:00
_ = lintCmd . Execute ( )
2026-03-04 11:59:23 +00:00
}
case "commit" :
if tc . Message != "" {
2026-03-04 12:05:01 +00:00
_ , _ = git . Run ( [ ] string { "commit" , "-m" , tc . Message } )
2026-03-04 11:59:23 +00:00
}
default :
color . Red ( "Unknown tool: %s" , tc . Tool )
}
* history = append ( * history , map [ string ] string { "role" : "assistant" , "content" : reply } )
}