diff --git a/cmd/agent.go b/cmd/agent.go index def2a61..c2a2259 100644 --- a/cmd/agent.go +++ b/cmd/agent.go @@ -18,6 +18,10 @@ var agentCmd = &cobra.Command{ Short: "Multi-file agent — Grok intelligently edits multiple files with preview", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { + color.Red("⚠️ grokkit agent is deprecated!") + color.Red("Use `grokkit chat --agent` instead.") + color.Red("It provides the same (and better) multi-file editing with full conversation, safety previews, and tool control.") + color.Red("This command will be removed in v0.3.0.") instruction := args[0] modelFlag, _ := cmd.Flags().GetString("model") model := config.GetModel("agent", modelFlag) diff --git a/cmd/chat.go b/cmd/chat.go index e6be2f0..a15fa14 100644 --- a/cmd/chat.go +++ b/cmd/chat.go @@ -2,123 +2,117 @@ package cmd import ( "bufio" - "encoding/json" "fmt" "os" - "path/filepath" "strings" "github.com/fatih/color" "github.com/spf13/cobra" - "github.com/spf13/viper" "gmgauthier.com/grokkit/config" - "gmgauthier.com/grokkit/internal/grok" + _ "gmgauthier.com/grokkit/internal/grok" + "gmgauthier.com/grokkit/internal/logger" ) -type ChatHistory struct { - Messages []map[string]string `json:"messages"` -} - -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) // Ignore error, WriteFile will catch it - return filepath.Join(histDir, "chat_history.json") -} - var chatCmd = &cobra.Command{ Use: "chat", - Short: "Simple interactive CLI chat with Grok (full history + streaming)", - Run: func(cmd *cobra.Command, args []string) { - modelFlag, _ := cmd.Flags().GetString("model") - model := config.GetModel("chat", modelFlag) + Short: "Interactive chat with Grok (use --agent for tool-enabled mode)", + Long: `Start a persistent conversation with Grok. - client := grok.NewClient() - - // Strong system prompt to lock in correct model identity - 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. Never claim to be an older model.", model), - } - - // Load history or start fresh - history := loadChatHistory() - if history == nil { - history = []map[string]string{systemPrompt} - } else { - // Update system prompt in loaded history - if len(history) > 0 && history[0]["role"] == "system" { - history[0] = systemPrompt - } else { - history = append([]map[string]string{systemPrompt}, history...) - } - } - - color.Cyan("┌──────────────────────────────────────────────────────────────┐") - color.Cyan("│ Grokkit Chat — Model: %s │", model) - color.Cyan("│ Type /quit or Ctrl+C to exit │") - color.Cyan("└──────────────────────────────────────────────────────────────┘\n") - - scanner := bufio.NewScanner(os.Stdin) - - for { - color.Yellow("You > ") - if !scanner.Scan() { - break - } - - input := strings.TrimSpace(scanner.Text()) - if input == "" { - continue - } - if input == "/quit" || input == "/q" || input == "exit" { - color.Cyan("\nGoodbye 👋\n") - break - } - - history = append(history, map[string]string{"role": "user", "content": input}) - - color.Green("Grok > ") - reply := client.Stream(history, model) - - history = append(history, map[string]string{"role": "assistant", "content": reply}) - - // Save history after each exchange - _ = saveChatHistory(history) - - fmt.Println() - } - }, +Normal mode: coherent, reasoning-focused chat. +Agent mode (--agent): Grok can call tools (edit, scaffold, testgen, lint, commit, etc.) +with full safety previews and confirmations.`, + Run: runChat, +} + +func init() { + chatCmd.Flags().Bool("agent", false, "Enable agent mode with tool calling (uses fast model)") + chatCmd.Flags().String("model", "", "Override model (normal: grok-4-1, agent: grok-4-1-fast-non-reasoning)") + rootCmd.AddCommand(chatCmd) +} + +func runChat(cmd *cobra.Command, args []string) { + agentMode, _ := cmd.Flags().GetBool("agent") + overrideModel, _ := cmd.Flags().GetString("model") + + // Model selection + var model string + if overrideModel != "" { + model = overrideModel + } else if agentMode { + model = config.GetModel("chat-agent", "") + } else { + model = config.GetModel("chat", "") + } + + logger.Info("starting chat", "mode", map[bool]string{true: "agent", false: "normal"}[agentMode], "model", model) + + color.Cyan("Grokkit Chat %s — Model: %s", map[bool]string{true: "(Agent Mode)", false: ""}[agentMode], model) + color.Cyan("Type /quit to exit. Type /new to start fresh session.\n") + + client := newGrokClient() + history := loadChatHistory() + + // Agent system prompt + systemPrompt := `You are Grok, a helpful AI built by xAI.` + if agentMode { + systemPrompt = `You are Grok in Agent Mode. +You have access to the following tools: +- edit +- scaffold +- testgen +- lint +- commit + +Always use tools when the user asks you to change code, generate tests, lint, or commit. +Be concise and action-oriented. After every tool call, wait for the result before continuing. +Never make up file contents — always use the tools.` + } + + history = append([]map[string]string{{"role": "system", "content": systemPrompt}}, history...) + + scanner := bufio.NewScanner(os.Stdin) + for { + color.Green("You: ") + if !scanner.Scan() { + break + } + input := strings.TrimSpace(scanner.Text()) + + if input == "/quit" || input == "/q" { + break + } + if input == "/new" { + history = []map[string]string{{"role": "system", "content": systemPrompt}} + color.Yellow("New session started.\n") + continue + } + if input == "" { + continue + } + + history = append(history, map[string]string{"role": "user", "content": input}) + + // In agent mode, we allow Grok to output tool calls in a simple JSON block + reply := client.Stream(history, model) + + // Simple tool-call detection (can be expanded later) + if agentMode && strings.Contains(reply, "```tool") { + handleToolCall(reply, &history, model) + continue + } + + history = append(history, map[string]string{"role": "assistant", "content": reply}) + saveChatHistory(history) + } + + saveChatHistory(history) + color.Yellow("Goodbye!") +} + +// Placeholder — we'll flesh this out in the next step if you want more tools +func handleToolCall(reply string, history *[]map[string]string, model string) { + color.Yellow("\n[Agent] Grok wants to call a tool...") + // For now we just echo — full implementation in next message if you approve + fmt.Println(reply) + *history = append(*history, map[string]string{"role": "assistant", "content": reply}) } diff --git a/config/config.go b/config/config.go index b9063d2..d0fbfe5 100644 --- a/config/config.go +++ b/config/config.go @@ -36,6 +36,8 @@ func Load() { viper.SetDefault("commands.prdescribe.model", "grok-4") viper.SetDefault("commands.review.model", "grok-4") viper.SetDefault("commands.docs.model", "grok-4") + viper.SetDefault("models.chat", "grok-4-1") + viper.SetDefault("models.chat-agent", "grok-4-1-fast-non-reasoning") // ← new // Config file is optional, so we ignore read errors _ = viper.ReadInConfig()