summaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
authorGrail Finder <wohilas@gmail.com>2026-03-17 09:23:35 +0300
committerGrail Finder <wohilas@gmail.com>2026-03-17 09:23:35 +0300
commit451e6f0381afe37c59f12703f8750407c27ba94a (patch)
treed4a76d7011f2da98bac2ee718afa587952c8ba4f /main.go
parent47b3d37a9714e3e68e56f009ea1faee5223edeb4 (diff)
parent326a1a4d094c6349e0403d479384347c52964537 (diff)
Merge branch 'master' into feat/agent-flow
Diffstat (limited to 'main.go')
-rw-r--r--main.go255
1 files changed, 255 insertions, 0 deletions
diff --git a/main.go b/main.go
index ddabff8..2a71920 100644
--- a/main.go
+++ b/main.go
@@ -1,6 +1,15 @@
package main
import (
+ "bufio"
+ "flag"
+ "fmt"
+ "gf-lt/models"
+ "gf-lt/pngmeta"
+ "os"
+ "slices"
+ "strconv"
+ "strings"
"sync/atomic"
"github.com/rivo/tview"
@@ -22,9 +31,22 @@ 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
+ cliContinue bool
+ cliMsg string
)
func main() {
+ flag.BoolVar(&cfg.CLIMode, "cli", false, "Run in CLI mode without TUI")
+ flag.BoolVar(&cfg.ToolUse, "tools", true, "run with tools")
+ flag.StringVar(&cliCardPath, "card", "", "Path to syscard JSON file")
+ flag.BoolVar(&cliContinue, "continue", false, "Continue from last chat (by agent or card)")
+ flag.StringVar(&cliMsg, "msg", "", "Send message and exit (one-shot mode)")
+ 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 +54,236 @@ 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)
+ }
+ if cliContinue {
+ if cliCardPath != "" {
+ history, err := loadAgentsLastChat(cfg.AssistantRole)
+ if err != nil {
+ fmt.Printf("No previous chat found for %s, starting new chat\n", cfg.AssistantRole)
+ startNewCLIChat()
+ } else {
+ chatBody.Messages = history
+ fmt.Printf("Continued chat: %s\n", activeChatName)
+ }
+ } else {
+ chatBody.Messages = loadOldChatOrGetNew()
+ fmt.Printf("Continued chat: %s\n", activeChatName)
+ }
+ } else {
+ startNewCLIChat()
+ }
+ printCLIWelcome()
+ go func() {
+ <-ctx.Done()
+ os.Exit(0)
+ }()
+ if cliMsg != "" {
+ persona := cfg.UserRole
+ if cfg.WriteNextMsgAs != "" {
+ persona = cfg.WriteNextMsgAs
+ }
+ chatRoundChan <- &models.ChatRoundReq{Role: persona, UserMsg: cliMsg}
+ <-cliRespDone
+ fmt.Println()
+ return
+ }
+ 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 <path>, /c <path> - Load a different syscard")
+ fmt.Println(" /undo, /u - Delete last message")
+ fmt.Println(" /history, /ls - List chat history")
+ fmt.Println(" /load <name> - Load a specific chat by name")
+ fmt.Println(" /model <name>, /m <name> - Switch model")
+ fmt.Println(" /api <index>, /a <index> - Switch API link (no index to list)")
+ 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.Printf("Current API: %s\n", cfg.CurrentAPI)
+ 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 <path>")
+ 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 <name>")
+ 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":
+ getModelListForAPI := func(api string) []string {
+ if strings.Contains(api, "api.deepseek.com/") {
+ return []string{"deepseek-chat", "deepseek-reasoner"}
+ } else if strings.Contains(api, "openrouter.ai") {
+ return ORFreeModels
+ }
+ return LocalModels
+ }
+ modelList := getModelListForAPI(cfg.CurrentAPI)
+ if len(args) == 0 {
+ fmt.Println("Models:")
+ for i, model := range modelList {
+ marker := " "
+ if model == chatBody.Model {
+ marker = "* "
+ }
+ fmt.Printf("%s%d: %s\n", marker, i, model)
+ }
+ fmt.Printf("\nCurrent model: %s\n", chatBody.Model)
+ return true
+ }
+ // Try index first, then model name
+ if idx, err := strconv.Atoi(args[0]); err == nil && idx >= 0 && idx < len(modelList) {
+ chatBody.Model = modelList[idx]
+ fmt.Printf("Switched to model: %s\n", chatBody.Model)
+ return true
+ }
+ if slices.Index(modelList, args[0]) < 0 {
+ fmt.Printf("Model '%s' not found. Use index or choose from:\n", args[0])
+ for i, model := range modelList {
+ fmt.Printf(" %d: %s\n", i, model)
+ }
+ return true
+ }
+ chatBody.Model = args[0]
+ fmt.Printf("Switched to model: %s\n", args[0])
+ case "/api", "/a":
+ if len(args) == 0 {
+ fmt.Println("API Links:")
+ for i, link := range cfg.ApiLinks {
+ marker := " "
+ if link == cfg.CurrentAPI {
+ marker = "* "
+ }
+ fmt.Printf("%s%d: %s\n", marker, i, link)
+ }
+ fmt.Printf("\nCurrent API: %s\n", cfg.CurrentAPI)
+ return true
+ }
+ idx := 0
+ fmt.Sscanf(args[0], "%d", &idx)
+ if idx < 0 || idx >= len(cfg.ApiLinks) {
+ fmt.Printf("Invalid index. Valid range: 0-%d\n", len(cfg.ApiLinks)-1)
+ return true
+ }
+ cfg.CurrentAPI = cfg.ApiLinks[idx]
+ fmt.Printf("Switched to API: %s\n", cfg.CurrentAPI)
+ 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
+}