From 2c9c36e2c6c00594fabd25f6d9daad91e038f93c Mon Sep 17 00:00:00 2001 From: Grail Finder Date: Sun, 15 Mar 2026 15:30:10 +0300 Subject: Feat: cli mode --- main.go | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) (limited to 'main.go') diff --git a/main.go b/main.go index ddabff8..2f02b8e 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,13 @@ package main import ( + "bufio" + "flag" + "fmt" + "gf-lt/models" + "gf-lt/pngmeta" + "os" + "strings" "sync/atomic" "github.com/rivo/tview" @@ -22,9 +29,19 @@ var ( statusLineTempl = "help (F12) | chat: [orange:-:b]%s[-:-:-] (F1) | [%s:-:b]tool use[-:-:-] (ctrl+k) | model: [%s:-:b]%s[-:-:-] (ctrl+l) | [%s:-:b]skip LLM resp[-:-:-] (F10) | API: [orange:-:b]%s[-:-:-] (ctrl+v)\nwriting as: [orange:-:b]%s[-:-:-] (ctrl+q) | bot will write as [orange:-:b]%s[-:-:-] (ctrl+x)" focusSwitcher = map[tview.Primitive]tview.Primitive{} app *tview.Application + cliCardPath string ) func main() { + flag.BoolVar(&cfg.CLIMode, "cli", false, "Run in CLI mode without TUI") + flag.StringVar(&cliCardPath, "card", "", "Path to syscard JSON file") + flag.Parse() + + if cfg.CLIMode { + runCLIMode() + return + } + pages.AddPage("main", flex, true, true) if err := app.SetRoot(pages, true).EnableMouse(cfg.EnableMouse).EnablePaste(true).Run(); err != nil { @@ -32,3 +49,164 @@ func main() { return } } + +func runCLIMode() { + outputHandler = &CLIOutputHandler{} + cliRespDone = make(chan bool, 1) + + if cliCardPath != "" { + card, err := pngmeta.ReadCardJson(cliCardPath) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to load syscard: %v\n", err) + os.Exit(1) + } + cfg.AssistantRole = card.Role + sysMap[card.ID] = card + roleToID[card.Role] = card.ID + charToStart(card.Role, false) + fmt.Printf("Loaded syscard: %s (%s)\n", card.Role, card.FilePath) + } + + startNewCLIChat() + + printCLIWelcome() + + go func() { + <-ctx.Done() + os.Exit(0) + }() + + scanner := bufio.NewScanner(os.Stdin) + for { + fmt.Print("> ") + if !scanner.Scan() { + break + } + msg := scanner.Text() + if msg == "" { + continue + } + + if strings.HasPrefix(msg, "/") { + if !handleCLICommand(msg) { + return + } + fmt.Println() + continue + } + + persona := cfg.UserRole + if cfg.WriteNextMsgAs != "" { + persona = cfg.WriteNextMsgAs + } + chatRoundChan <- &models.ChatRoundReq{Role: persona, UserMsg: msg} + <-cliRespDone + fmt.Println() + } +} + +func printCLIWelcome() { + fmt.Println("CLI Mode started. Type your messages or commands.") + fmt.Println("Type /help for available commands.") + fmt.Println() +} + +func printCLIHelp() { + fmt.Println("Available commands:") + fmt.Println(" /help, /h - Show this help message") + fmt.Println(" /new, /n - Start a new chat (clears conversation)") + fmt.Println(" /card , /c - Load a different syscard") + fmt.Println(" /undo, /u - Delete last message") + fmt.Println(" /history, /ls - List chat history") + fmt.Println(" /load - Load a specific chat by name") + fmt.Println(" /model , /m - Switch model") + fmt.Println(" /quit, /q, /exit - Exit CLI mode") + fmt.Println() + fmt.Printf("Current syscard: %s\n", cfg.AssistantRole) + fmt.Printf("Current model: %s\n", chatBody.Model) + fmt.Println() +} + +func handleCLICommand(msg string) bool { + parts := strings.Fields(msg) + cmd := strings.ToLower(parts[0]) + args := parts[1:] + + switch cmd { + case "/help", "/h": + printCLIHelp() + case "/new", "/n": + startNewCLIChat() + fmt.Println("New chat started.") + fmt.Printf("Syscard: %s\n", cfg.AssistantRole) + fmt.Println() + case "/card", "/c": + if len(args) == 0 { + fmt.Println("Usage: /card ") + return true + } + card, err := pngmeta.ReadCardJson(args[0]) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to load syscard: %v\n", err) + return true + } + cfg.AssistantRole = card.Role + sysMap[card.ID] = card + roleToID[card.Role] = card.ID + charToStart(card.Role, false) + startNewCLIChat() + fmt.Printf("Switched to syscard: %s (%s)\n", card.Role, card.FilePath) + case "/undo", "/u": + if len(chatBody.Messages) == 0 { + fmt.Println("No messages to delete.") + return true + } + chatBody.Messages = chatBody.Messages[:len(chatBody.Messages)-1] + cliPrevOutput = "" + fmt.Println("Last message deleted.") + case "/history", "/ls": + fmt.Println("Chat history:") + for name := range chatMap { + marker := " " + if name == activeChatName { + marker = "* " + } + fmt.Printf("%s%s\n", marker, name) + } + fmt.Println() + case "/load": + if len(args) == 0 { + fmt.Println("Usage: /load ") + return true + } + name := args[0] + chat, ok := chatMap[name] + if !ok { + fmt.Printf("Chat not found: %s\n", name) + return true + } + history, err := chat.ToHistory() + if err != nil { + fmt.Printf("Failed to load chat: %v\n", err) + return true + } + chatBody.Messages = history + activeChatName = name + cfg.AssistantRole = chat.Agent + fmt.Printf("Loaded chat: %s\n", name) + case "/model", "/m": + if len(args) == 0 { + fmt.Printf("Current model: %s\n", chatBody.Model) + return true + } + chatBody.Model = args[0] + fmt.Printf("Switched to model: %s\n", args[0]) + case "/quit", "/q", "/exit": + fmt.Println("Goodbye!") + return false + default: + fmt.Printf("Unknown command: %s\n", msg) + fmt.Println("Type /help for available commands.") + } + return true +} -- cgit v1.2.3