mirror of
https://github.com/Direct-Dev-Ru/go-lcg.git
synced 2025-11-15 17:20:00 +00:00
1193 lines
45 KiB
Go
1193 lines
45 KiB
Go
package main
|
||
|
||
import (
|
||
_ "embed"
|
||
"encoding/json"
|
||
"fmt"
|
||
"math"
|
||
"os"
|
||
"os/exec"
|
||
"os/user"
|
||
"path/filepath"
|
||
"slices"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/atotto/clipboard"
|
||
cmdPackage "github.com/direct-dev-ru/linux-command-gpt/cmd"
|
||
"github.com/direct-dev-ru/linux-command-gpt/config"
|
||
"github.com/direct-dev-ru/linux-command-gpt/gpt"
|
||
"github.com/direct-dev-ru/linux-command-gpt/reader"
|
||
"github.com/direct-dev-ru/linux-command-gpt/serve"
|
||
"github.com/direct-dev-ru/linux-command-gpt/validation"
|
||
"github.com/urfave/cli/v2"
|
||
"gopkg.in/yaml.v3"
|
||
)
|
||
|
||
//go:embed VERSION.txt
|
||
var Version string
|
||
|
||
//go:embed build-conditions.yaml
|
||
var BuildConditionsFromYaml string
|
||
|
||
type buildConditions struct {
|
||
NoServe bool `yaml:"no-serve"`
|
||
}
|
||
|
||
var CompileConditions buildConditions
|
||
|
||
// disableHistory управляет записью/обновлением истории на уровне процесса (флаг имеет приоритет над env)
|
||
var disableHistory bool
|
||
|
||
// fromHistory указывает, что текущий ответ взят из истории
|
||
var fromHistory bool
|
||
|
||
const (
|
||
colorRed = "\033[31m"
|
||
colorGreen = "\033[32m"
|
||
colorYellow = "\033[33m"
|
||
colorBlue = "\033[34m"
|
||
colorPurple = "\033[35m"
|
||
colorCyan = "\033[36m"
|
||
colorReset = "\033[0m"
|
||
colorBold = "\033[1m"
|
||
)
|
||
|
||
func main() {
|
||
|
||
if err := yaml.Unmarshal([]byte(BuildConditionsFromYaml), &CompileConditions); err != nil {
|
||
fmt.Println("Error parsing build conditions:", err)
|
||
CompileConditions.NoServe = false
|
||
}
|
||
|
||
// fmt.Println("Build conditions:", CompileConditions)
|
||
|
||
_ = colorBlue
|
||
|
||
gpt.InitBuiltinPrompts("")
|
||
|
||
// Авто-инициализация sys_prompts при старте CLI (создаст файл при отсутствии)
|
||
if currentUser, err := user.Current(); err == nil {
|
||
_ = gpt.NewPromptManager(currentUser.HomeDir)
|
||
}
|
||
|
||
app := &cli.App{
|
||
Name: "lcg",
|
||
Usage: config.AppConfig.AppName + " - Генерация Linux команд из описаний",
|
||
Version: Version,
|
||
Commands: getCommands(),
|
||
Before: func(c *cli.Context) error {
|
||
// Применяем флаги приложения к конфигурации перед выполнением любой команды
|
||
// Это гарантирует, что флаги будут применены даже для команд, которые не используют основной Action
|
||
applyAppFlagsToConfig(c)
|
||
return nil
|
||
},
|
||
UsageText: `
|
||
lcg [опции] <описание команды>
|
||
|
||
Примеры:
|
||
lcg "хочу извлечь файл linux-command-gpt.tar.gz"
|
||
lcg --file /path/to/file.txt "хочу вывести все директории с помощью ls"
|
||
`,
|
||
Description: `
|
||
{{.AppName}} - инструмент для генерации Linux команд из описаний на естественном языке.
|
||
Поддерживает чтение частей промпта из файлов и позволяет сохранять, копировать или перегенерировать результаты.
|
||
может задавать системный промпт или выбирать из предустановленных промптов.
|
||
|
||
Переменные окружения:
|
||
|
||
Основные настройки:
|
||
LCG_HOST Endpoint для LLM API (по умолчанию: http://192.168.87.108:11434/)
|
||
LCG_MODEL Название модели (по умолчанию: hf.co/yandex/YandexGPT-5-Lite-8B-instruct-GGUF:Q4_K_M)
|
||
LCG_PROMPT Текст промпта по умолчанию
|
||
LCG_PROVIDER Тип провайдера: "ollama" или "proxy" (по умолчанию: ollama)
|
||
LCG_JWT_TOKEN JWT токен для proxy провайдера
|
||
LCG_PROMPT_ID ID промпта по умолчанию (по умолчанию: 1)
|
||
LCG_TIMEOUT Таймаут запроса в секундах (по умолчанию: 300)
|
||
LCG_COMPLETIONS_PATH Путь к API для завершений (по умолчанию: api/chat)
|
||
LCG_PROXY_URL URL прокси для proxy провайдера (по умолчанию: /api/v1/protected/sberchat/chat)
|
||
LCG_API_KEY_FILE Файл с API ключом (по умолчанию: .openai_api_key)
|
||
LCG_APP_NAME Название приложения (по умолчанию: Linux Command GPT)
|
||
LCG_ALLOW_THINK только для ollama: разрешить модели отправлять свои размышления ("1" или "true" = разрешено, пусто = запрещено). Имеет смысл для моделей, которые поддерживают эти действия: qwen3, deepseek.
|
||
|
||
Настройки истории и выполнения:
|
||
LCG_NO_HISTORY Отключить запись истории ("1" или "true" = отключено, пусто = включено)
|
||
LCG_ALLOW_EXECUTION Разрешить выполнение команд ("1" или "true" = разрешено, пусто = запрещено)
|
||
LCG_RESULT_FOLDER Папка для сохранения результатов (по умолчанию: ~/.config/lcg/gpt_results)
|
||
LCG_RESULT_HISTORY Файл истории результатов (по умолчанию: <result_folder>/lcg_history.json)
|
||
LCG_PROMPT_FOLDER Папка для системных промптов (по умолчанию: ~/.config/lcg/gpt_sys_prompts)
|
||
LCG_CONFIG_FOLDER Папка для конфигурации (по умолчанию: ~/.config/lcg/config)
|
||
|
||
Настройки сервера (команда serve):
|
||
LCG_SERVER_PORT Порт сервера (по умолчанию: 8080)
|
||
LCG_SERVER_HOST Хост сервера (по умолчанию: localhost)
|
||
LCG_SERVER_ALLOW_HTTP Разрешить HTTP соединения ("true" для localhost, "false" для других хостов)
|
||
LCG_SERVER_REQUIRE_AUTH Требовать аутентификацию ("1" или "true" = требуется, пусто = не требуется)
|
||
LCG_SERVER_PASSWORD Пароль администратора (по умолчанию: admin#123456)
|
||
LCG_SERVER_SSL_CERT_FILE Путь к SSL сертификату
|
||
LCG_SERVER_SSL_KEY_FILE Путь к приватному ключу SSL
|
||
LCG_DOMAIN Домен для сервера (по умолчанию: значение LCG_SERVER_HOST)
|
||
LCG_COOKIE_SECURE Безопасные cookie ("1" или "true" = включено, пусто = выключено)
|
||
LCG_COOKIE_PATH Путь для cookie (по умолчанию: /lcg)
|
||
LCG_COOKIE_TTL_HOURS Время жизни cookie в часах (по умолчанию: 168)
|
||
LCG_BASE_URL Базовый URL приложения (по умолчанию: /lcg)
|
||
LCG_HEALTH_URL URL для проверки здоровья API (по умолчанию: /api/v1/protected/sberchat/health)
|
||
|
||
Настройки валидации:
|
||
LCG_MAX_SYSTEM_PROMPT_LENGTH Максимальная длина системного промпта (по умолчанию: 2000)
|
||
LCG_MAX_USER_MESSAGE_LENGTH Максимальная длина пользовательского сообщения (по умолчанию: 4000)
|
||
LCG_MAX_PROMPT_NAME_LENGTH Максимальная длина названия промпта (по умолчанию: 2000)
|
||
LCG_MAX_PROMPT_DESC_LENGTH Максимальная длина описания промпта (по умолчанию: 5000)
|
||
LCG_MAX_COMMAND_LENGTH Максимальная длина команды (по умолчанию: 8000)
|
||
LCG_MAX_EXPLANATION_LENGTH Максимальная длина объяснения (по умолчанию: 20000)
|
||
|
||
Отладка и браузер:
|
||
LCG_DEBUG Включить режим отладки ("1" или "true" = включено, пусто = выключено)
|
||
LCG_BROWSER_PATH Путь к браузеру для автоматического открытия (команда serve --browser)
|
||
`,
|
||
Flags: []cli.Flag{
|
||
&cli.StringFlag{
|
||
Name: "file",
|
||
Aliases: []string{"f"},
|
||
Usage: "Read part of the command from a file",
|
||
},
|
||
&cli.StringFlag{
|
||
Name: "model",
|
||
Aliases: []string{"M"},
|
||
DefaultText: "Use model from LCG_MODEL or default model",
|
||
Usage: "Model to use",
|
||
},
|
||
&cli.BoolFlag{
|
||
Name: "no-history",
|
||
Aliases: []string{"nh"},
|
||
Usage: "Disable writing/updating command history (overrides LCG_NO_HISTORY)",
|
||
Value: false,
|
||
},
|
||
&cli.BoolFlag{
|
||
Name: "think",
|
||
Aliases: []string{"T"},
|
||
Usage: "Разрешить модели отправлять свои размышления",
|
||
Value: false,
|
||
},
|
||
&cli.StringFlag{
|
||
Name: "query",
|
||
Aliases: []string{"Q"},
|
||
Usage: "Query to send to the model",
|
||
DefaultText: "Привет! Порадуй меня случайной Linux командой ...",
|
||
Value: "Привет! Порадуй меня случайной Linux командой ...",
|
||
},
|
||
&cli.StringFlag{
|
||
Name: "sys",
|
||
Aliases: []string{"s"},
|
||
Usage: "System prompt content or ID",
|
||
DefaultText: "Use prompt ID from LCG_PROMPT_ID or default prompt",
|
||
Value: "",
|
||
},
|
||
&cli.IntFlag{
|
||
Name: "prompt-id",
|
||
Aliases: []string{"pid"},
|
||
Usage: "System prompt ID (1-5 for default prompts)",
|
||
DefaultText: "1",
|
||
Value: 1,
|
||
},
|
||
&cli.IntFlag{
|
||
Name: "timeout",
|
||
Aliases: []string{"t"},
|
||
Usage: "Request timeout in seconds",
|
||
DefaultText: "120",
|
||
Value: 120,
|
||
},
|
||
&cli.BoolFlag{
|
||
Name: "debug",
|
||
Aliases: []string{"d"},
|
||
Usage: "Show debug information (request parameters and prompts)",
|
||
Value: false,
|
||
},
|
||
},
|
||
Action: func(c *cli.Context) error {
|
||
file := c.String("file")
|
||
system := c.String("sys")
|
||
model := c.String("model")
|
||
query := c.String("query")
|
||
// обновляем конфиг на основе флагов
|
||
if c.IsSet("sys") && system != "" {
|
||
config.AppConfig.Prompt = system
|
||
}
|
||
if c.IsSet("query") && query != "" {
|
||
config.AppConfig.Query = query
|
||
}
|
||
if c.IsSet("timeout") {
|
||
config.AppConfig.Timeout = fmt.Sprintf("%d", c.Int("timeout"))
|
||
}
|
||
if c.IsSet("model") {
|
||
config.AppConfig.Model = model
|
||
}
|
||
config.AppConfig.Think = false
|
||
if c.IsSet("think") {
|
||
config.AppConfig.Think = c.Bool("think")
|
||
}
|
||
promptID := c.Int("prompt-id")
|
||
timeout := c.Int("timeout")
|
||
|
||
config.AppConfig.MainFlags = config.MainFlags{
|
||
File: file,
|
||
NoHistory: c.Bool("no-history"),
|
||
Sys: system,
|
||
PromptID: promptID,
|
||
Timeout: timeout,
|
||
Debug: c.Bool("debug"),
|
||
}
|
||
disableHistory = config.AppConfig.MainFlags.NoHistory || config.AppConfig.IsNoHistoryEnabled()
|
||
|
||
config.AppConfig.MainFlags.Debug = config.AppConfig.MainFlags.Debug || config.GetEnvBool("LCG_DEBUG", false)
|
||
|
||
args := c.Args().Slice()
|
||
|
||
if len(args) == 0 && config.AppConfig.Query == "" {
|
||
cli.ShowAppHelp(c)
|
||
showTips()
|
||
return nil
|
||
}
|
||
|
||
// Если указан prompt-id, загружаем соответствующий промпт
|
||
if system == "" && promptID > 0 {
|
||
currentUser, _ := user.Current()
|
||
pm := gpt.NewPromptManager(currentUser.HomeDir)
|
||
if prompt, err := pm.GetPromptByID(promptID); err == nil {
|
||
system = prompt.Content
|
||
} else {
|
||
fmt.Printf("Warning: Prompt ID %d not found, using default prompt\n", promptID)
|
||
}
|
||
}
|
||
|
||
if CompileConditions.NoServe {
|
||
if len(args) > 1 && args[0] == "serve" {
|
||
printColored("❌ Error: serve command is disabled in this build\n", colorRed)
|
||
os.Exit(1)
|
||
}
|
||
}
|
||
|
||
if config.AppConfig.Query != "" {
|
||
executeMain(file, system, config.AppConfig.Query, timeout)
|
||
return nil
|
||
}
|
||
|
||
executeMain(file, system, strings.Join(args, " "), timeout)
|
||
return nil
|
||
},
|
||
}
|
||
|
||
cli.VersionFlag = &cli.BoolFlag{
|
||
Name: "version",
|
||
Aliases: []string{"V", "v"},
|
||
Usage: "prints out version",
|
||
}
|
||
cli.VersionPrinter = func(cCtx *cli.Context) {
|
||
fmt.Printf("%s\n", cCtx.App.Version)
|
||
}
|
||
|
||
if err := app.Run(os.Args); err != nil {
|
||
fmt.Println("Error:", err)
|
||
os.Exit(1)
|
||
}
|
||
}
|
||
|
||
// applyAppFlagsToConfig применяет флаги приложения к конфигурации
|
||
// Работает как для основного Action, так и для команд
|
||
func applyAppFlagsToConfig(c *cli.Context) {
|
||
// Применяем флаг model - проверяем и через IsSet, и значение напрямую
|
||
// так как IsSet может не работать для флагов без значения по умолчанию
|
||
if model := c.String("model"); model != "" {
|
||
config.AppConfig.Model = model
|
||
}
|
||
|
||
// Применяем флаг sys
|
||
if sys := c.String("sys"); sys != "" {
|
||
config.AppConfig.Prompt = sys
|
||
}
|
||
|
||
// Применяем флаг timeout (только если явно установлен)
|
||
if c.IsSet("timeout") {
|
||
config.AppConfig.Timeout = fmt.Sprintf("%d", c.Int("timeout"))
|
||
}
|
||
|
||
// Применяем флаг query (игнорируем значение по умолчанию)
|
||
if query := c.String("query"); query != "" && query != "Hello? what day is it today?" {
|
||
config.AppConfig.Query = query
|
||
}
|
||
}
|
||
|
||
func getCommands() []*cli.Command {
|
||
commands := []*cli.Command{
|
||
{
|
||
Name: "update-key",
|
||
Aliases: []string{"u"},
|
||
Usage: "Update the API key",
|
||
Action: func(c *cli.Context) error {
|
||
if config.AppConfig.ProviderType == "ollama" || config.AppConfig.ProviderType == "proxy" {
|
||
fmt.Println("API key is not needed for ollama and proxy providers")
|
||
return nil
|
||
}
|
||
timeout := 120 // default timeout
|
||
if t, err := strconv.Atoi(config.AppConfig.Timeout); err == nil {
|
||
timeout = t
|
||
}
|
||
gpt3 := initGPT(config.AppConfig.Prompt, timeout)
|
||
gpt3.UpdateKey()
|
||
fmt.Println("API key updated.")
|
||
return nil
|
||
},
|
||
},
|
||
{
|
||
Name: "delete-key",
|
||
Aliases: []string{"d"},
|
||
Usage: "Delete the API key",
|
||
Action: func(c *cli.Context) error {
|
||
if config.AppConfig.ProviderType == "ollama" || config.AppConfig.ProviderType == "proxy" {
|
||
fmt.Println("API key is not needed for ollama and proxy providers")
|
||
return nil
|
||
}
|
||
timeout := 120 // default timeout
|
||
if t, err := strconv.Atoi(config.AppConfig.Timeout); err == nil {
|
||
timeout = t
|
||
}
|
||
gpt3 := initGPT(config.AppConfig.Prompt, timeout)
|
||
gpt3.DeleteKey()
|
||
fmt.Println("API key deleted.")
|
||
return nil
|
||
},
|
||
},
|
||
{
|
||
Name: "update-jwt",
|
||
Aliases: []string{"j"},
|
||
Usage: "Update the JWT token for proxy API",
|
||
Action: func(c *cli.Context) error {
|
||
if config.AppConfig.ProviderType != "proxy" {
|
||
fmt.Println("JWT token is only needed for proxy provider")
|
||
return nil
|
||
}
|
||
|
||
var jwtToken string
|
||
fmt.Print("JWT Token: ")
|
||
fmt.Scanln(&jwtToken)
|
||
|
||
currentUser, _ := user.Current()
|
||
jwtFile := currentUser.HomeDir + "/.proxy_jwt_token"
|
||
if err := os.WriteFile(jwtFile, []byte(strings.TrimSpace(jwtToken)), 0600); err != nil {
|
||
fmt.Printf("Ошибка сохранения JWT токена: %v\n", err)
|
||
return err
|
||
}
|
||
|
||
fmt.Println("JWT token updated.")
|
||
return nil
|
||
},
|
||
},
|
||
{
|
||
Name: "delete-jwt",
|
||
Aliases: []string{"dj"},
|
||
Usage: "Delete the JWT token for proxy API",
|
||
Action: func(c *cli.Context) error {
|
||
if config.AppConfig.ProviderType != "proxy" {
|
||
fmt.Println("JWT token is only needed for proxy provider")
|
||
return nil
|
||
}
|
||
|
||
currentUser, _ := user.Current()
|
||
jwtFile := currentUser.HomeDir + "/.proxy_jwt_token"
|
||
if err := os.Remove(jwtFile); err != nil && !os.IsNotExist(err) {
|
||
fmt.Printf("Ошибка удаления JWT токена: %v\n", err)
|
||
return err
|
||
}
|
||
|
||
fmt.Println("JWT token deleted.")
|
||
return nil
|
||
},
|
||
},
|
||
{
|
||
Name: "models",
|
||
Aliases: []string{"m"},
|
||
Usage: "Show available models",
|
||
Action: func(c *cli.Context) error {
|
||
timeout := 120 // default timeout
|
||
if t, err := strconv.Atoi(config.AppConfig.Timeout); err == nil {
|
||
timeout = t
|
||
}
|
||
gpt3 := initGPT(config.AppConfig.Prompt, timeout)
|
||
models, err := gpt3.GetAvailableModels()
|
||
if err != nil {
|
||
fmt.Printf("Ошибка получения моделей: %v\n", err)
|
||
return err
|
||
}
|
||
|
||
fmt.Printf("Доступные модели для провайдера %s:\n", config.AppConfig.ProviderType)
|
||
for i, model := range models {
|
||
fmt.Printf(" %d. %s\n", i+1, model)
|
||
}
|
||
return nil
|
||
},
|
||
},
|
||
{
|
||
Name: "health",
|
||
Aliases: []string{"he"}, // Изменено с "h" на "he"
|
||
Usage: "Check API health",
|
||
Action: func(c *cli.Context) error {
|
||
timeout := 120 // default timeout
|
||
if t, err := strconv.Atoi(config.AppConfig.Timeout); err == nil {
|
||
timeout = t
|
||
}
|
||
gpt3 := initGPT(config.AppConfig.Prompt, timeout)
|
||
if err := gpt3.Health(); err != nil {
|
||
fmt.Printf("Health check failed: %v\n", err)
|
||
return err
|
||
}
|
||
fmt.Println("API is healthy.")
|
||
return nil
|
||
},
|
||
},
|
||
{
|
||
Name: "config",
|
||
Aliases: []string{"co"}, // Изменено с "c" на "co"
|
||
Usage: "Show current configuration",
|
||
Flags: []cli.Flag{
|
||
&cli.BoolFlag{
|
||
Name: "full",
|
||
Aliases: []string{"f"},
|
||
Usage: "Show full configuration object",
|
||
},
|
||
},
|
||
Action: func(c *cli.Context) error {
|
||
// Флаги приложения уже применены через глобальный Before hook
|
||
// Но применяем их еще раз на случай, если глобальный Before не сработал
|
||
applyAppFlagsToConfig(c)
|
||
|
||
if c.Bool("full") {
|
||
// Выводим полную конфигурацию в JSON формате
|
||
showFullConfig()
|
||
} else {
|
||
// Выводим краткую конфигурацию
|
||
showShortConfig()
|
||
}
|
||
return nil
|
||
},
|
||
},
|
||
{
|
||
Name: "history",
|
||
Aliases: []string{"hist"},
|
||
Usage: "Show command history",
|
||
Subcommands: []*cli.Command{
|
||
{
|
||
Name: "list",
|
||
Aliases: []string{"l"},
|
||
Usage: "List history entries",
|
||
Action: func(c *cli.Context) error {
|
||
if disableHistory {
|
||
printColored("📝 История отключена (--no-history / LCG_NO_HISTORY)\n", colorYellow)
|
||
} else {
|
||
cmdPackage.ShowHistory(config.AppConfig.ResultHistory, printColored, colorYellow)
|
||
}
|
||
return nil
|
||
},
|
||
},
|
||
{
|
||
Name: "view",
|
||
Aliases: []string{"v"},
|
||
Usage: "View history entry by ID",
|
||
Action: func(c *cli.Context) error {
|
||
if c.NArg() == 0 {
|
||
fmt.Println("Укажите ID записи истории")
|
||
return nil
|
||
}
|
||
var id int
|
||
if _, err := fmt.Sscanf(c.Args().First(), "%d", &id); err != nil || id <= 0 {
|
||
fmt.Println("Неверный ID")
|
||
return nil
|
||
}
|
||
if disableHistory {
|
||
fmt.Println("История отключена")
|
||
} else {
|
||
cmdPackage.ViewHistoryEntry(config.AppConfig.ResultHistory, id, printColored, colorYellow, colorBold, colorGreen)
|
||
}
|
||
return nil
|
||
},
|
||
},
|
||
{
|
||
Name: "delete",
|
||
Aliases: []string{"d"},
|
||
Usage: "Delete history entry by ID",
|
||
Action: func(c *cli.Context) error {
|
||
if c.NArg() == 0 {
|
||
fmt.Println("Укажите ID записи истории")
|
||
return nil
|
||
}
|
||
var id int
|
||
if _, err := fmt.Sscanf(c.Args().First(), "%d", &id); err != nil || id <= 0 {
|
||
fmt.Println("Неверный ID")
|
||
return nil
|
||
}
|
||
if disableHistory {
|
||
fmt.Println("История отключена")
|
||
} else if err := cmdPackage.DeleteHistoryEntry(config.AppConfig.ResultHistory, id); err != nil {
|
||
fmt.Println(err)
|
||
}
|
||
return nil
|
||
},
|
||
},
|
||
},
|
||
},
|
||
{
|
||
Name: "prompts",
|
||
Aliases: []string{"p"},
|
||
Usage: "Manage system prompts",
|
||
Subcommands: []*cli.Command{
|
||
{
|
||
Name: "list",
|
||
Aliases: []string{"l"},
|
||
Usage: "List all available prompts",
|
||
Flags: []cli.Flag{
|
||
&cli.BoolFlag{
|
||
Name: "full",
|
||
Aliases: []string{"f"},
|
||
Usage: "Show full content without truncation",
|
||
},
|
||
},
|
||
Action: func(c *cli.Context) error {
|
||
currentUser, _ := user.Current()
|
||
pm := gpt.NewPromptManager(currentUser.HomeDir)
|
||
full := c.Bool("full")
|
||
pm.ListPromptsWithFull(full)
|
||
return nil
|
||
},
|
||
},
|
||
{
|
||
Name: "add",
|
||
Aliases: []string{"a"},
|
||
Usage: "Add a new custom prompt",
|
||
Action: func(c *cli.Context) error {
|
||
currentUser, _ := user.Current()
|
||
pm := gpt.NewPromptManager(currentUser.HomeDir)
|
||
|
||
var name, description, content string
|
||
|
||
fmt.Print("Название промпта: ")
|
||
fmt.Scanln(&name)
|
||
|
||
fmt.Print("Описание: ")
|
||
fmt.Scanln(&description)
|
||
|
||
fmt.Print("Содержание промпта: ")
|
||
fmt.Scanln(&content)
|
||
|
||
if err := pm.AddCustomPrompt(name, description, content); err != nil {
|
||
fmt.Printf("Ошибка добавления промпта: %v\n", err)
|
||
return err
|
||
}
|
||
|
||
fmt.Println("Промпт успешно добавлен!")
|
||
return nil
|
||
},
|
||
},
|
||
{
|
||
Name: "delete",
|
||
Aliases: []string{"d"},
|
||
Usage: "Delete a custom prompt",
|
||
Action: func(c *cli.Context) error {
|
||
if c.NArg() == 0 {
|
||
fmt.Println("Укажите ID промпта для удаления")
|
||
return nil
|
||
}
|
||
|
||
var id int
|
||
if _, err := fmt.Sscanf(c.Args().First(), "%d", &id); err != nil {
|
||
fmt.Println("Неверный ID промпта")
|
||
return err
|
||
}
|
||
|
||
currentUser, _ := user.Current()
|
||
pm := gpt.NewPromptManager(currentUser.HomeDir)
|
||
|
||
if err := pm.DeleteCustomPrompt(id); err != nil {
|
||
fmt.Printf("Ошибка удаления промпта: %v\n", err)
|
||
return err
|
||
}
|
||
|
||
fmt.Println("Промпт успешно удален!")
|
||
return nil
|
||
},
|
||
},
|
||
},
|
||
},
|
||
{
|
||
Name: "test-prompt",
|
||
Aliases: []string{"tp"},
|
||
Usage: "Test a specific prompt ID",
|
||
Action: func(c *cli.Context) error {
|
||
if c.NArg() == 0 {
|
||
fmt.Println("Usage: lcg test-prompt <prompt-id> <command>")
|
||
return nil
|
||
}
|
||
|
||
var promptID int
|
||
if _, err := fmt.Sscanf(c.Args().First(), "%d", &promptID); err != nil {
|
||
fmt.Println("Invalid prompt ID")
|
||
return err
|
||
}
|
||
|
||
currentUser, _ := user.Current()
|
||
pm := gpt.NewPromptManager(currentUser.HomeDir)
|
||
|
||
prompt, err := pm.GetPromptByID(promptID)
|
||
if err != nil {
|
||
fmt.Printf("Prompt ID %d not found\n", promptID)
|
||
return err
|
||
}
|
||
|
||
fmt.Printf("Testing prompt ID %d: %s\n", promptID, prompt.Name)
|
||
fmt.Printf("Description: %s\n", prompt.Description)
|
||
fmt.Printf("Content: %s\n", prompt.Content)
|
||
|
||
if len(c.Args().Slice()) > 1 {
|
||
command := strings.Join(c.Args().Slice()[1:], " ")
|
||
fmt.Printf("\nTesting with command: %s\n", command)
|
||
timeout := 120 // default timeout
|
||
if t, err := strconv.Atoi(config.AppConfig.Timeout); err == nil {
|
||
timeout = t
|
||
}
|
||
executeMain("", prompt.Content, command, timeout)
|
||
}
|
||
|
||
return nil
|
||
},
|
||
},
|
||
{
|
||
Name: "serve",
|
||
Usage: "Start HTTP server to browse saved results",
|
||
Flags: []cli.Flag{
|
||
&cli.StringFlag{
|
||
Name: "port",
|
||
Aliases: []string{"p"},
|
||
Usage: "Server port",
|
||
Value: config.AppConfig.Server.Port,
|
||
},
|
||
&cli.StringFlag{
|
||
Name: "host",
|
||
Aliases: []string{"H"},
|
||
Usage: "Server host",
|
||
Value: config.AppConfig.Server.Host,
|
||
},
|
||
&cli.BoolFlag{
|
||
Name: "browser",
|
||
Aliases: []string{"b"},
|
||
Usage: "Open browser automatically after starting server",
|
||
Value: false,
|
||
},
|
||
},
|
||
Action: func(c *cli.Context) error {
|
||
port := c.String("port")
|
||
host := c.String("host")
|
||
openBrowser := c.Bool("browser")
|
||
|
||
// Пробрасываем debug: флаг или переменная окружения LCG_DEBUG
|
||
// Позволяет запускать: LCG_DEBUG=1 lcg serve ... или lcg -d serve ...
|
||
config.AppConfig.MainFlags.Debug = c.Bool("debug") || config.GetEnvBool("LCG_DEBUG", false)
|
||
|
||
// Обновляем конфигурацию сервера с новыми параметрами
|
||
config.AppConfig.Server.Host = host
|
||
config.AppConfig.Server.Port = port
|
||
// Пересчитываем AllowHTTP на основе нового хоста
|
||
config.AppConfig.Server.AllowHTTP = getServerAllowHTTPForHost(host)
|
||
|
||
// Определяем протокол на основе хоста
|
||
useHTTPS := !config.AppConfig.Server.AllowHTTP
|
||
protocol := "http"
|
||
if useHTTPS {
|
||
protocol = "https"
|
||
}
|
||
|
||
printColored(fmt.Sprintf("🌐 Запускаю %s сервер на %s:%s\n", strings.ToUpper(protocol), host, port), colorCyan)
|
||
printColored(fmt.Sprintf("📁 Папка результатов: %s\n", config.AppConfig.ResultFolder), colorYellow)
|
||
|
||
// Предупреждение о самоподписанном сертификате
|
||
if useHTTPS {
|
||
printColored("⚠️ Используется самоподписанный SSL сертификат\n", colorYellow)
|
||
printColored(" Браузер может показать предупреждение о безопасности\n", colorYellow)
|
||
printColored(" Нажмите 'Дополнительно' → 'Перейти на сайт' для продолжения\n", colorYellow)
|
||
}
|
||
|
||
// Для автооткрытия браузера заменяем 0.0.0.0 на localhost
|
||
browserHost := host
|
||
if host == "0.0.0.0" {
|
||
browserHost = "localhost"
|
||
}
|
||
|
||
// Учитываем BasePath в URL
|
||
basePath := config.AppConfig.Server.BasePath
|
||
if basePath == "" || basePath == "/" {
|
||
basePath = ""
|
||
} else {
|
||
basePath = strings.TrimSuffix(basePath, "/")
|
||
}
|
||
url := fmt.Sprintf("%s://%s:%s%s", protocol, browserHost, port, basePath)
|
||
|
||
if openBrowser {
|
||
printColored("🌍 Открываю браузер...\n", colorGreen)
|
||
if err := openBrowserURL(url); err != nil {
|
||
printColored(fmt.Sprintf("⚠️ Не удалось открыть браузер: %v\n", err), colorYellow)
|
||
printColored("📱 Откройте браузер вручную и перейдите по адресу: ", colorGreen)
|
||
printColored(url+"\n", colorYellow)
|
||
}
|
||
} else {
|
||
printColored("🔗 Откройте в браузере: ", colorGreen)
|
||
printColored(url+"\n", colorYellow)
|
||
}
|
||
|
||
return serve.StartResultServer(host, port)
|
||
},
|
||
},
|
||
}
|
||
|
||
if CompileConditions.NoServe {
|
||
filteredCommands := []*cli.Command{}
|
||
for _, cmd := range commands {
|
||
if cmd.Name != "serve" {
|
||
filteredCommands = append(filteredCommands, cmd)
|
||
}
|
||
}
|
||
commands = filteredCommands
|
||
}
|
||
|
||
return commands
|
||
|
||
}
|
||
|
||
func executeMain(file, system, commandInput string, timeout int) {
|
||
// Валидация длины пользовательского сообщения
|
||
if err := validation.ValidateUserMessage(commandInput); err != nil {
|
||
printColored(fmt.Sprintf("❌ Ошибка: %s\n", err.Error()), colorRed)
|
||
return
|
||
}
|
||
|
||
// Валидация длины системного промпта
|
||
if err := validation.ValidateSystemPrompt(system); err != nil {
|
||
printColored(fmt.Sprintf("❌ Ошибка: %s\n", err.Error()), colorRed)
|
||
return
|
||
}
|
||
|
||
// Выводим debug информацию если включен флаг
|
||
if config.AppConfig.MainFlags.Debug {
|
||
printDebugInfo(file, system, commandInput, timeout)
|
||
}
|
||
if file != "" {
|
||
if err := reader.FileToPrompt(&commandInput, file); err != nil {
|
||
printColored(fmt.Sprintf("❌ Ошибка чтения файла: %v\n", err), colorRed)
|
||
return
|
||
}
|
||
}
|
||
|
||
// Если system пустой, используем дефолтный промпт
|
||
if system == "" {
|
||
system = config.AppConfig.Prompt
|
||
}
|
||
|
||
// Обеспечим папку результатов заранее (может понадобиться при действиях)
|
||
if _, err := os.Stat(config.AppConfig.ResultFolder); os.IsNotExist(err) {
|
||
if err := os.MkdirAll(config.AppConfig.ResultFolder, 0755); err != nil {
|
||
printColored(fmt.Sprintf("❌ Ошибка создания папки результатов: %v\n", err), colorRed)
|
||
return
|
||
}
|
||
}
|
||
|
||
// Проверка истории: если такой запрос уже встречался — предложить открыть из истории
|
||
if !disableHistory {
|
||
if found, hist := cmdPackage.CheckAndSuggestFromHistory(config.AppConfig.ResultHistory, commandInput); found && hist != nil {
|
||
fromHistory = true // Устанавливаем флаг, что ответ из истории
|
||
gpt3 := initGPT(system, timeout)
|
||
printColored("\nВНИМАНИЕ: ОТВЕТ СФОРМИРОВАН ИИ. ТРЕБУЕТСЯ ПРОВЕРКА И КРИТИЧЕСКИЙ АНАЛИЗ. ВОЗМОЖНЫ ОШИБКИ И ГАЛЛЮЦИНАЦИИ.\n", colorRed)
|
||
printColored("\n📋 Команда (из истории):\n", colorYellow)
|
||
printColored(fmt.Sprintf(" %s\n\n", hist.Response), colorBold+colorGreen)
|
||
if strings.TrimSpace(hist.Explanation) != "" {
|
||
printColored("\n📖 Подробное объяснение (из истории):\n\n", colorYellow)
|
||
fmt.Println(hist.Explanation)
|
||
}
|
||
// Показали из истории — не выполняем запрос к API, сразу меню действий
|
||
handlePostResponse(hist.Response, gpt3, system, commandInput, timeout, hist.Explanation)
|
||
return
|
||
}
|
||
}
|
||
|
||
// Папка уже создана выше
|
||
|
||
gpt3 := initGPT(system, timeout)
|
||
|
||
printColored("🤖 Запрос: ", colorCyan)
|
||
fmt.Printf("%s\n", commandInput)
|
||
|
||
response, elapsed := getCommand(gpt3, commandInput)
|
||
if response == "" {
|
||
printColored("❌ Ответ не получен. Проверьте подключение к API.\n", colorRed)
|
||
return
|
||
}
|
||
|
||
printColored(fmt.Sprintf("✅ Выполнено за %.2f сек\n", elapsed), colorGreen)
|
||
// Обязательное предупреждение перед первым ответом
|
||
printColored("\nВНИМАНИЕ: ОТВЕТ СФОРМИРОВАН ИИ. ТРЕБУЕТСЯ ПРОВЕРКА И КРИТИЧЕСКИЙ АНАЛИЗ. ВОЗМОЖНЫ ОШИБКИ И ГАЛЛЮЦИНАЦИИ.\n", colorRed)
|
||
printColored("\n📋 Команда:\n", colorYellow)
|
||
printColored(fmt.Sprintf(" %s\n\n", response), colorBold+colorGreen)
|
||
|
||
// Сохраняем в историю (после завершения работы – т.е. позже, в зависимости от выбора действия)
|
||
// Здесь не сохраняем, чтобы учесть правило: сохранять после действия, отличного от v/vv/vvv
|
||
fromHistory = false // Сбрасываем флаг для новых запросов
|
||
handlePostResponse(response, gpt3, system, commandInput, timeout, "")
|
||
}
|
||
|
||
// checkAndSuggestFromHistory проверяет файл истории и при совпадении запроса предлагает показать сохраненный результат
|
||
// moved to history.go
|
||
|
||
func initGPT(system string, timeout int) gpt.Gpt3 {
|
||
currentUser, _ := user.Current()
|
||
|
||
// Загружаем JWT токен в зависимости от провайдера
|
||
var jwtToken string
|
||
if config.AppConfig.ProviderType == "proxy" {
|
||
jwtToken = config.AppConfig.JwtToken
|
||
if jwtToken == "" {
|
||
// Пытаемся загрузить из файла
|
||
jwtFile := currentUser.HomeDir + "/.proxy_jwt_token"
|
||
if data, err := os.ReadFile(jwtFile); err == nil {
|
||
jwtToken = strings.TrimSpace(string(data))
|
||
}
|
||
}
|
||
}
|
||
|
||
return *gpt.NewGpt3(config.AppConfig.ProviderType, config.AppConfig.Host, jwtToken, config.AppConfig.Model, system, 0.01, timeout)
|
||
}
|
||
|
||
func getCommand(gpt3 gpt.Gpt3, cmd string) (string, float64) {
|
||
gpt3.InitKey()
|
||
start := time.Now()
|
||
done := make(chan bool)
|
||
|
||
go func() {
|
||
loadingChars := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
|
||
i := 0
|
||
for {
|
||
select {
|
||
case <-done:
|
||
fmt.Printf("\r%s", strings.Repeat(" ", 50))
|
||
fmt.Print("\r")
|
||
return
|
||
default:
|
||
fmt.Printf("\r%s Обрабатываю запрос...", loadingChars[i])
|
||
i = (i + 1) % len(loadingChars)
|
||
time.Sleep(100 * time.Millisecond)
|
||
}
|
||
}
|
||
}()
|
||
|
||
response := gpt3.Completions(cmd)
|
||
done <- true
|
||
elapsed := math.Round(time.Since(start).Seconds()*100) / 100
|
||
|
||
return response, elapsed
|
||
}
|
||
|
||
func handlePostResponse(response string, gpt3 gpt.Gpt3, system, cmd string, timeout int, explanation string) {
|
||
// Формируем меню действий
|
||
menu := "Действия: (c)копировать, (s)сохранить, (r)перегенерировать"
|
||
if config.AppConfig.AllowExecution {
|
||
menu += ", (e)выполнить"
|
||
}
|
||
menu += ", (v|vv|vvv)подробно, (n)ничего: "
|
||
|
||
fmt.Print(menu)
|
||
var choice string
|
||
fmt.Scanln(&choice)
|
||
|
||
switch strings.ToLower(choice) {
|
||
case "c":
|
||
clipboard.WriteAll(response)
|
||
fmt.Println("✅ Команда скопирована в буфер обмена")
|
||
if !disableHistory {
|
||
if fromHistory {
|
||
cmdPackage.SaveToHistoryFromHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt, explanation)
|
||
} else {
|
||
cmdPackage.SaveToHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt)
|
||
}
|
||
}
|
||
case "s":
|
||
if fromHistory && strings.TrimSpace(explanation) != "" {
|
||
saveResponse(response, gpt3.Model, gpt3.Prompt, cmd, explanation)
|
||
} else {
|
||
saveResponse(response, gpt3.Model, gpt3.Prompt, cmd)
|
||
}
|
||
if !disableHistory {
|
||
if fromHistory {
|
||
cmdPackage.SaveToHistoryFromHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt, explanation)
|
||
} else {
|
||
cmdPackage.SaveToHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt)
|
||
}
|
||
}
|
||
case "r":
|
||
fmt.Println("🔄 Перегенерирую...")
|
||
executeMain("", system, cmd, timeout)
|
||
case "e":
|
||
if config.AppConfig.AllowExecution {
|
||
executeCommand(response)
|
||
if !disableHistory {
|
||
if fromHistory {
|
||
cmdPackage.SaveToHistoryFromHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt, explanation)
|
||
} else {
|
||
cmdPackage.SaveToHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt)
|
||
}
|
||
}
|
||
} else {
|
||
fmt.Println("⚠️ Выполнение команд отключено. Установите LCG_ALLOW_EXECUTION=1 для включения этой функции.")
|
||
}
|
||
case "v", "vv", "vvv":
|
||
level := len(choice) // 1, 2, 3
|
||
deps := cmdPackage.ExplainDeps{
|
||
DisableHistory: disableHistory,
|
||
PrintColored: printColored,
|
||
ColorPurple: colorPurple,
|
||
ColorGreen: colorGreen,
|
||
ColorRed: colorRed,
|
||
ColorYellow: colorYellow,
|
||
GetCommand: getCommand,
|
||
}
|
||
cmdPackage.ShowDetailedExplanation(response, gpt3, system, cmd, timeout, level, deps)
|
||
default:
|
||
fmt.Println(" До свидания!")
|
||
if !disableHistory {
|
||
if fromHistory {
|
||
cmdPackage.SaveToHistoryFromHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt, explanation)
|
||
} else {
|
||
cmdPackage.SaveToHistory(config.AppConfig.ResultHistory, config.AppConfig.ResultFolder, cmd, response, gpt3.Prompt)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// moved to response.go
|
||
|
||
// saveExplanation сохраняет подробное объяснение и альтернативные способы
|
||
// moved to explain.go
|
||
|
||
// truncateTitle сокращает строку до 120 символов (по рунам), добавляя " ..." при усечении
|
||
// moved to response.go
|
||
|
||
// moved to explain.go
|
||
|
||
func executeCommand(command string) {
|
||
fmt.Printf("🚀 Выполняю: %s\n", command)
|
||
fmt.Print("Продолжить? (y/N): ")
|
||
var confirm string
|
||
fmt.Scanln(&confirm)
|
||
|
||
if strings.ToLower(confirm) == "y" || strings.ToLower(confirm) == "yes" {
|
||
cmd := exec.Command("bash", "-c", command)
|
||
cmd.Stdout = os.Stdout
|
||
cmd.Stderr = os.Stderr
|
||
|
||
if err := cmd.Run(); err != nil {
|
||
fmt.Printf("❌ Ошибка выполнения: %v\n", err)
|
||
} else {
|
||
fmt.Println("✅ Команда выполнена успешно")
|
||
}
|
||
} else {
|
||
fmt.Println("❌ Выполнение отменено")
|
||
}
|
||
}
|
||
|
||
// env helpers moved to config package
|
||
|
||
// moved to history.go
|
||
|
||
func printColored(text, color string) {
|
||
fmt.Printf("%s%s%s", color, text, colorReset)
|
||
}
|
||
|
||
func showTips() {
|
||
printColored("💡 Подсказки:\n", colorCyan)
|
||
fmt.Println(" • Используйте --file для чтения из файла")
|
||
fmt.Println(" • Используйте --sys для изменения системного промпта")
|
||
fmt.Println(" • Используйте --prompt-id для выбора предустановленного промпта")
|
||
fmt.Println(" • Используйте --timeout для установки таймаута запроса")
|
||
fmt.Println(" • Укажите --no-history чтобы не записывать историю (аналог LCG_NO_HISTORY)")
|
||
fmt.Println(" • Команда 'prompts list' покажет все доступные промпты")
|
||
fmt.Println(" • Команда 'history list' покажет историю запросов")
|
||
fmt.Println(" • Команда 'config' покажет текущие настройки")
|
||
fmt.Println(" • Команда 'health' проверит доступность API")
|
||
fmt.Println(" • Команда 'serve' запустит HTTP сервер для просмотра результатов")
|
||
fmt.Println(" • Используйте --browser для автоматического открытия браузера")
|
||
fmt.Println(" • Установите LCG_BROWSER_PATH для указания конкретного браузера")
|
||
}
|
||
|
||
// printDebugInfo выводит отладочную информацию о параметрах запроса
|
||
func printDebugInfo(file, system, commandInput string, timeout int) {
|
||
printColored("\n🔍 DEBUG ИНФОРМАЦИЯ:\n", colorCyan)
|
||
fmt.Printf("📁 Файл: %s\n", file)
|
||
fmt.Printf("🤖 Системный промпт: %s\n", system)
|
||
fmt.Printf("💬 Запрос: %s\n", commandInput)
|
||
fmt.Printf("⏱️ Таймаут: %d сек\n", timeout)
|
||
fmt.Printf("🌐 Провайдер: %s\n", config.AppConfig.ProviderType)
|
||
fmt.Printf("🏠 Хост: %s\n", config.AppConfig.Host)
|
||
fmt.Printf("🧠 Модель: %s\n", config.AppConfig.Model)
|
||
fmt.Printf("📝 История: %t\n", !config.AppConfig.MainFlags.NoHistory)
|
||
printColored("────────────────────────────────────────\n", colorCyan)
|
||
}
|
||
|
||
// openBrowserURL открывает URL в браузере
|
||
func openBrowserURL(url string) error {
|
||
// Проверяем переменную окружения LCG_BROWSER_PATH
|
||
if browserPath := os.Getenv("LCG_BROWSER_PATH"); browserPath != "" {
|
||
return exec.Command(browserPath, url).Start()
|
||
}
|
||
|
||
// Список браузеров в порядке приоритета
|
||
browsers := []string{
|
||
"yandex-browser", // Яндекс.Браузер
|
||
"yandex-browser-stable", // Яндекс.Браузер (стабильная версия)
|
||
"firefox", // Mozilla Firefox
|
||
"firefox-esr", // Firefox ESR
|
||
"google-chrome", // Google Chrome
|
||
"google-chrome-stable", // Google Chrome (стабильная версия)
|
||
"chromium", // Chromium
|
||
"chromium-browser", // Chromium (Ubuntu/Debian)
|
||
}
|
||
|
||
// Стандартные пути для поиска браузеров
|
||
paths := []string{
|
||
"/usr/bin",
|
||
"/usr/local/bin",
|
||
"/opt/google/chrome",
|
||
"/opt/yandex/browser",
|
||
"/snap/bin",
|
||
"/usr/lib/chromium-browser",
|
||
}
|
||
|
||
// Ищем браузер в указанном порядке
|
||
for _, browser := range browsers {
|
||
for _, path := range paths {
|
||
fullPath := filepath.Join(path, browser)
|
||
if _, err := os.Stat(fullPath); err == nil {
|
||
return exec.Command(fullPath, url).Start()
|
||
}
|
||
}
|
||
// Также пробуем найти в PATH
|
||
if _, err := exec.LookPath(browser); err == nil {
|
||
return exec.Command(browser, url).Start()
|
||
}
|
||
}
|
||
|
||
return fmt.Errorf("не найден ни один из поддерживаемых браузеров")
|
||
}
|
||
|
||
// getServerAllowHTTPForHost определяет AllowHTTP для конкретного хоста
|
||
func getServerAllowHTTPForHost(host string) bool {
|
||
// Если переменная явно установлена, используем её
|
||
if value, exists := os.LookupEnv("LCG_SERVER_ALLOW_HTTP"); exists {
|
||
if boolValue, err := strconv.ParseBool(value); err == nil {
|
||
return boolValue
|
||
}
|
||
}
|
||
|
||
// Если переменная не установлена, определяем по умолчанию на основе хоста
|
||
return isSecureHost(host)
|
||
}
|
||
|
||
// isSecureHost проверяет, является ли хост безопасным для HTTP
|
||
func isSecureHost(host string) bool {
|
||
secureHosts := []string{"localhost", "127.0.0.1", "::1"}
|
||
return slices.Contains(secureHosts, host)
|
||
}
|
||
|
||
// showShortConfig показывает краткую конфигурацию
|
||
func showShortConfig() {
|
||
fmt.Printf("Provider: %s\n", config.AppConfig.ProviderType)
|
||
fmt.Printf("Host: %s\n", config.AppConfig.Host)
|
||
fmt.Printf("Model: %s\n", config.AppConfig.Model)
|
||
fmt.Printf("Prompt: %s\n", config.AppConfig.Prompt)
|
||
fmt.Printf("Timeout: %s seconds\n", config.AppConfig.Timeout)
|
||
if config.AppConfig.ProviderType == "proxy" {
|
||
fmt.Printf("JWT Token: %s\n", func() string {
|
||
if config.AppConfig.JwtToken != "" {
|
||
return "***set***"
|
||
}
|
||
currentUser, _ := user.Current()
|
||
jwtFile := currentUser.HomeDir + "/.proxy_jwt_token"
|
||
if _, err := os.Stat(jwtFile); err == nil {
|
||
return "***from file***"
|
||
}
|
||
return "***not set***"
|
||
}())
|
||
}
|
||
}
|
||
|
||
// showFullConfig показывает полную конфигурацию в JSON формате
|
||
func showFullConfig() {
|
||
// Создаем структуру для безопасного вывода (скрываем чувствительные данные)
|
||
type SafeConfig struct {
|
||
Cwd string `json:"cwd"`
|
||
Host string `json:"host"`
|
||
Completions string `json:"completions"`
|
||
Model string `json:"model"`
|
||
Prompt string `json:"prompt"`
|
||
ApiKeyFile string `json:"api_key_file"`
|
||
ResultFolder string `json:"result_folder"`
|
||
PromptFolder string `json:"prompt_folder"`
|
||
ProviderType string `json:"provider_type"`
|
||
JwtToken string `json:"jwt_token"` // Показываем статус, не сам токен
|
||
PromptID string `json:"prompt_id"`
|
||
Timeout string `json:"timeout"`
|
||
ResultHistory string `json:"result_history"`
|
||
NoHistoryEnv string `json:"no_history_env"`
|
||
AllowExecution bool `json:"allow_execution"`
|
||
MainFlags config.MainFlags `json:"main_flags"`
|
||
Server config.ServerConfig `json:"server"`
|
||
Validation config.ValidationConfig `json:"validation"`
|
||
}
|
||
|
||
// Создаем безопасную копию конфигурации
|
||
safeConfig := SafeConfig{
|
||
Cwd: config.AppConfig.Cwd,
|
||
Host: config.AppConfig.Host,
|
||
Completions: config.AppConfig.Completions,
|
||
Model: config.AppConfig.Model,
|
||
Prompt: config.AppConfig.Prompt,
|
||
ApiKeyFile: config.AppConfig.ApiKeyFile,
|
||
ResultFolder: config.AppConfig.ResultFolder,
|
||
PromptFolder: config.AppConfig.PromptFolder,
|
||
ProviderType: config.AppConfig.ProviderType,
|
||
JwtToken: func() string {
|
||
if config.AppConfig.JwtToken != "" {
|
||
return "***set***"
|
||
}
|
||
currentUser, _ := user.Current()
|
||
jwtFile := currentUser.HomeDir + "/.proxy_jwt_token"
|
||
if _, err := os.Stat(jwtFile); err == nil {
|
||
return "***from file***"
|
||
}
|
||
return "***not set***"
|
||
}(),
|
||
PromptID: config.AppConfig.PromptID,
|
||
Timeout: config.AppConfig.Timeout,
|
||
ResultHistory: config.AppConfig.ResultHistory,
|
||
NoHistoryEnv: config.AppConfig.NoHistoryEnv,
|
||
AllowExecution: config.AppConfig.AllowExecution,
|
||
MainFlags: config.AppConfig.MainFlags,
|
||
Server: config.AppConfig.Server,
|
||
Validation: config.AppConfig.Validation,
|
||
}
|
||
|
||
safeConfig.Server.Password = "***"
|
||
|
||
// Выводим JSON с отступами
|
||
jsonData, err := json.MarshalIndent(safeConfig, "", " ")
|
||
if err != nil {
|
||
fmt.Printf("Ошибка сериализации конфигурации: %v\n", err)
|
||
return
|
||
}
|
||
|
||
fmt.Println(string(jsonData))
|
||
}
|