Исправления в ветке main

This commit is contained in:
2026-10-22 12:42:32 +06:00
parent 933e5ccb58
commit 9b05edc263
31 changed files with 2433 additions and 114 deletions

View File

@@ -9,29 +9,34 @@ import (
)
type Config struct {
Cwd string
Host string
ProxyUrl string
AppName string
Completions string
Model string
Prompt string
ApiKeyFile string
ConfigFolder string
ResultFolder string
PromptFolder string
ProviderType string
JwtToken string
PromptID string
Timeout string
ResultHistory string
NoHistoryEnv string
AllowExecution bool
Think bool
Query string
MainFlags MainFlags
Server ServerConfig
Validation ValidationConfig
Cwd string
Host string
ProxyUrl string
AppName string
Completions string
Model string
Prompt string
ApiKeyFile string
ConfigFolder string
ResultFolder string
PromptFolder string
ProviderType string
JwtToken string
PromptID string
Timeout string
Retries int
ResultHistory string
NoHistoryEnv string
AllowExecution bool
Think bool
Query string
OutputMode string
OutputFile string
ContextMaxBytes int
BackupCount int
MainFlags MainFlags
Server ServerConfig
Validation ValidationConfig
}
type MainFlags struct {
@@ -41,6 +46,7 @@ type MainFlags struct {
PromptID int
Timeout int
Debug bool
DebugFile string
}
type ServerConfig struct {
@@ -117,6 +123,16 @@ func isSecureHost(host string) bool {
func Load() Config {
cwd, _ := os.Getwd()
providerType := getEnv("LCG_PROVIDER", "ollama")
hostDefault := "http://192.168.87.108:11434/"
completionsDefault := "api/chat"
apiKeyFileDefault := ".openai_api_key"
if providerType == "apimira" {
hostDefault = "https://apimira.com"
completionsDefault = "/v1/chat/completions"
apiKeyFileDefault = ".apimira_api_key"
}
homedir, err := os.UserHomeDir()
if err != nil {
homedir = cwd
@@ -134,24 +150,29 @@ func Load() Config {
configFolder := getEnv("LCG_CONFIG_FOLDER", privateConfigDir)
return Config{
Cwd: cwd,
AppName: getEnv("LCG_APP_NAME", "Linux Command GPT"),
Host: getEnv("LCG_HOST", "http://192.168.87.108:11434/"),
ProxyUrl: getEnv("LCG_PROXY_URL", "/api/v1/protected/sberchat/chat"),
Completions: getEnv("LCG_COMPLETIONS_PATH", "api/chat"),
Model: getEnv("LCG_MODEL", "hf.co/yandex/YandexGPT-5-Lite-8B-instruct-GGUF:Q4_K_M"),
Prompt: getEnv("LCG_PROMPT", "Reply with linux command and nothing else. Output with plain response - no need formatting. No need explanation. No need code blocks. No need ` symbols."),
ApiKeyFile: getEnv("LCG_API_KEY_FILE", ".openai_api_key"),
ResultFolder: resultFolder,
PromptFolder: promptFolder,
ConfigFolder: configFolder,
ProviderType: getEnv("LCG_PROVIDER", "ollama"),
JwtToken: getEnv("LCG_JWT_TOKEN", ""),
PromptID: getEnv("LCG_PROMPT_ID", "1"),
Timeout: getEnv("LCG_TIMEOUT", "300"),
ResultHistory: getEnv("LCG_RESULT_HISTORY", path.Join(resultFolder, "lcg_history.json")),
NoHistoryEnv: getEnv("LCG_NO_HISTORY", ""),
AllowExecution: isAllowExecutionEnabled(),
Cwd: cwd,
AppName: getEnv("LCG_APP_NAME", "Linux Command GPT"),
Host: getEnv("LCG_HOST", hostDefault),
ProxyUrl: getEnv("LCG_PROXY_URL", "/api/v1/protected/sberchat/chat"),
Completions: getEnv("LCG_COMPLETIONS_PATH", completionsDefault),
Model: getEnv("LCG_MODEL", "hf.co/yandex/YandexGPT-5-Lite-8B-instruct-GGUF:Q4_K_M"),
Prompt: getEnv("LCG_PROMPT", "Reply with linux command and nothing else. Output with plain response - no need formatting. No need explanation. No need code blocks. No need ` symbols."),
ApiKeyFile: getEnv("LCG_API_KEY_FILE", apiKeyFileDefault),
ResultFolder: resultFolder,
PromptFolder: promptFolder,
ConfigFolder: configFolder,
ProviderType: providerType,
JwtToken: getEnv("LCG_JWT_TOKEN", ""),
OutputMode: getEnv("LCG_MODE", "stdout"),
OutputFile: getEnv("LCG_OUTPUT_FILE", "lcg-out-script.sh"),
ContextMaxBytes: getEnvInt("LCG_CONTEXT_MAX_BYTES", 16000),
BackupCount: getEnvInt("LCG_BACKUP_COUNT", 3),
PromptID: getEnv("LCG_PROMPT_ID", "1"),
Timeout: getEnv("LCG_TIMEOUT", "300"),
Retries: getEnvInt("LCG_RETRIES", 3),
ResultHistory: getEnv("LCG_RESULT_HISTORY", path.Join(resultFolder, "lcg_history.json")),
NoHistoryEnv: getEnv("LCG_NO_HISTORY", ""),
AllowExecution: isAllowExecutionEnabled(),
Server: ServerConfig{
Port: getEnv("LCG_SERVER_PORT", "8080"),
Host: getEnv("LCG_SERVER_HOST", "localhost"),

43
config/debug.go Normal file
View File

@@ -0,0 +1,43 @@
package config
import (
"fmt"
"os"
"sync"
"time"
)
var (
debugFileMu sync.Mutex
debugFileWarned bool
)
// DebugLogf пишет строку в debug-файл (флаг --debug-file или переменная LCG_DEBUG_FILE),
// если он задан. Файл открывается в режиме дозаписи с правами 0600,
// каждая запись получает отметку времени. Ошибка открытия выводится один раз в stderr.
func DebugLogf(format string, args ...any) {
path := AppConfig.MainFlags.DebugFile
if path == "" {
path = os.Getenv("LCG_DEBUG_FILE")
}
if path == "" {
return
}
line := fmt.Sprintf(format, args...)
debugFileMu.Lock()
defer debugFileMu.Unlock()
file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
if !debugFileWarned {
debugFileWarned = true
fmt.Fprintf(os.Stderr, "⚠️ Не удалось открыть debug-файл %s: %v\n", path, err)
}
return
}
defer file.Close()
fmt.Fprintf(file, "[%s] %s\n", time.Now().Format("2006-01-02 15:04:05.000"), line)
}

28
config/filename.go Normal file
View File

@@ -0,0 +1,28 @@
package config
import "strings"
// SafeFileName делает строку безопасной для использования в имени файла:
// недопустимые символы (включая "/" из формата моделей поставщик/имя) заменяются на "-",
// пустой результат заменяется на "unknown".
func SafeFileName(s string) string {
replacer := strings.NewReplacer(
"/", "-",
"\\", "-",
":", "-",
"*", "-",
"?", "-",
"\"", "-",
"<", "-",
">", "-",
"|", "-",
"\n", "-",
"\r", "-",
"\t", "-",
)
result := strings.TrimSpace(replacer.Replace(s))
if result == "" {
return "unknown"
}
return result
}