mirror of
https://github.com/Direct-Dev-Ru/go-lcg.git
synced 2025-11-16 01:29:55 +00:00
233 lines
6.1 KiB
Go
233 lines
6.1 KiB
Go
package main
|
|
|
|
import (
|
|
_ "embed"
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"os/user"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/atotto/clipboard"
|
|
"github.com/direct-dev-ru/linux-command-gpt/gpt"
|
|
"github.com/direct-dev-ru/linux-command-gpt/reader"
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
//go:embed VERSION.txt
|
|
var Version string
|
|
|
|
var (
|
|
cwd, _ = os.Getwd()
|
|
HOST = getEnv("LCG_HOST", "http://192.168.87.108:11434/")
|
|
COMPLETIONS = getEnv("LCG_COMPLETIONS_PATH", "api/chat")
|
|
MODEL = getEnv("LCG_MODEL", "codegeex4")
|
|
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.")
|
|
API_KEY_FILE = getEnv("LCG_API_KEY_FILE", ".openai_api_key")
|
|
RESULT_FOLDER = getEnv("LCG_RESULT_FOLDER", path.Join(cwd, "gpt_results"))
|
|
)
|
|
|
|
func main() {
|
|
app := &cli.App{
|
|
Name: "lcg",
|
|
Usage: "Linux Command GPT - Generate Linux commands from descriptions",
|
|
Version: Version,
|
|
Commands: getCommands(),
|
|
UsageText: `
|
|
lcg [global options] <command description>
|
|
|
|
Examples:
|
|
lcg "I want to extract linux-command-gpt.tar.gz file"
|
|
lcg --file /path/to/file.txt "I want to list all directories with ls"
|
|
`,
|
|
Description: `
|
|
Linux Command GPT is a tool for generating Linux commands from natural language descriptions.
|
|
It supports reading parts of the prompt from files and allows saving, copying, or regenerating results.
|
|
Additional commands are available for managing API keys.
|
|
|
|
Environment Variables:
|
|
LCG_HOST Endpoint for LLM API (default: http://192.168.87.108:11434/)
|
|
LCG_COMPLETIONS_PATH Relative API path (default: api/chat)
|
|
LCG_MODEL Model name (default: codegeex4)
|
|
LCG_PROMPT Default prompt text
|
|
LCG_API_KEY_FILE API key storage file (default: ~/.openai_api_key)
|
|
LCG_RESULT_FOLDER Results folder (default: ./gpt_results)
|
|
`,
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "file",
|
|
Aliases: []string{"f"},
|
|
Usage: "Read part of the command from a file",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "sys",
|
|
Aliases: []string{"s"},
|
|
Usage: "System prompt",
|
|
DefaultText: getEnv("LCG_PROMPT", "Reply with linux command and nothing else. Output with plain response - no need formatting. No need explanation. No need code blocks"),
|
|
Value: getEnv("LCG_PROMPT", "Reply with linux command and nothing else. Output with plain response - no need formatting. No need explanation. No need code blocks"),
|
|
},
|
|
},
|
|
Action: func(c *cli.Context) error {
|
|
file := c.String("file")
|
|
system := c.String("sys")
|
|
args := c.Args().Slice()
|
|
if len(args) == 0 {
|
|
cli.ShowAppHelp(c)
|
|
return nil
|
|
}
|
|
executeMain(file, system, strings.Join(args, " "))
|
|
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)
|
|
}
|
|
}
|
|
|
|
func getCommands() []*cli.Command {
|
|
return []*cli.Command{
|
|
{
|
|
Name: "update-key",
|
|
Aliases: []string{"u"},
|
|
Usage: "Update the API key",
|
|
Action: func(c *cli.Context) error {
|
|
gpt3 := initGPT(PROMPT)
|
|
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 {
|
|
gpt3 := initGPT(PROMPT)
|
|
gpt3.DeleteKey()
|
|
fmt.Println("API key deleted.")
|
|
return nil
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func executeMain(file, system, commandInput string) {
|
|
// fmt.Println(system, commandInput)
|
|
// os.Exit(0)
|
|
if file != "" {
|
|
if err := reader.FileToPrompt(&commandInput, file); err != nil {
|
|
fmt.Println("Error reading file:", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
if _, err := os.Stat(RESULT_FOLDER); os.IsNotExist(err) {
|
|
os.MkdirAll(RESULT_FOLDER, 0755)
|
|
}
|
|
|
|
gpt3 := initGPT(system)
|
|
|
|
// if system != PROMPT {
|
|
// commandInput += ". " + system
|
|
// }
|
|
fmt.Println(commandInput)
|
|
response, elapsed := getCommand(gpt3, commandInput)
|
|
if response == "" {
|
|
fmt.Println("No response received.")
|
|
return
|
|
}
|
|
|
|
fmt.Printf("Completed in %v seconds\n\n%s\n", elapsed, response)
|
|
handlePostResponse(response, gpt3, system, commandInput)
|
|
}
|
|
|
|
func initGPT(system string) gpt.Gpt3 {
|
|
currentUser, _ := user.Current()
|
|
return gpt.Gpt3{
|
|
CompletionUrl: HOST + COMPLETIONS,
|
|
Model: MODEL,
|
|
Prompt: system,
|
|
HomeDir: currentUser.HomeDir,
|
|
ApiKeyFile: API_KEY_FILE,
|
|
Temperature: 0.01,
|
|
}
|
|
}
|
|
|
|
func getCommand(gpt3 gpt.Gpt3, cmd string) (string, float64) {
|
|
gpt3.InitKey()
|
|
start := time.Now()
|
|
done := make(chan bool)
|
|
go func() {
|
|
loadingChars := []rune{'-', '\\', '|', '/'}
|
|
i := 0
|
|
for {
|
|
select {
|
|
case <-done:
|
|
fmt.Printf("\r")
|
|
return
|
|
default:
|
|
fmt.Printf("\rLoading %c", loadingChars[i])
|
|
i = (i + 1) % len(loadingChars)
|
|
time.Sleep(30 * 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) {
|
|
fmt.Print("\nOptions: (c)opy, (s)ave, (r)egenerate, (n)one: ")
|
|
var choice string
|
|
fmt.Scanln(&choice)
|
|
|
|
switch strings.ToLower(choice) {
|
|
case "c":
|
|
clipboard.WriteAll(response)
|
|
fmt.Println("Response copied to clipboard.")
|
|
case "s":
|
|
saveResponse(response, gpt3, cmd)
|
|
case "r":
|
|
executeMain("", system, cmd)
|
|
default:
|
|
fmt.Println("No action taken.")
|
|
}
|
|
}
|
|
|
|
func saveResponse(response string, gpt3 gpt.Gpt3, cmd string) {
|
|
timestamp := time.Now().Format("2006-01-02_15-04-05")
|
|
filename := fmt.Sprintf("gpt_request_%s_%s.md", gpt3.Model, timestamp)
|
|
filePath := path.Join(RESULT_FOLDER, filename)
|
|
content := fmt.Sprintf("## Prompt:\n\n%s\n\n## Response:\n\n%s\n", cmd+". "+gpt3.Prompt, response)
|
|
|
|
if err := os.WriteFile(filePath, []byte(content), 0644); err != nil {
|
|
fmt.Println("Failed to save response:", err)
|
|
} else {
|
|
fmt.Printf("Response saved to %s\n", filePath)
|
|
}
|
|
}
|
|
|
|
func getEnv(key, defaultValue string) string {
|
|
if value, exists := os.LookupEnv(key); exists {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|