mirror of
https://github.com/Direct-Dev-Ru/go-lcg.git
synced 2025-11-16 01:29:55 +00:00
first commit
This commit is contained in:
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.test
|
||||||
|
*.out
|
||||||
|
go.work
|
||||||
|
*.log
|
||||||
|
lcg
|
||||||
|
|
||||||
33
README.md
Normal file
33
README.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
## Linux Command GPT (lcg)
|
||||||
|
Get Linux commands in natural language with the power of ChatGPT.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
Build from source
|
||||||
|
```bash
|
||||||
|
> git clone --depth 1 https://github.com/asrul10/linux-command-gpt.git ~/.linux-command-gpt
|
||||||
|
> cd linux-command-gpt
|
||||||
|
> go build -o lcg
|
||||||
|
# Add to your environment $PATH
|
||||||
|
> ln -s ~/.local/bin/fd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
> lcg I want to extract file linux-command-gpt.tar.gz
|
||||||
|
Completed in 0.92 seconds
|
||||||
|
┌────────────────────────────────────┐
|
||||||
|
│ tar -xvzf linux-command-gpt.tar.gz │
|
||||||
|
└────────────────────────────────────┘
|
||||||
|
Are you sure you want to execute the command? (Y/n):
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
```bash
|
||||||
|
> gpt3 [options]
|
||||||
|
|
||||||
|
--help output usage information
|
||||||
|
--version output the version number
|
||||||
|
--update-key update the API key
|
||||||
|
--delete-key delete the API key
|
||||||
|
```
|
||||||
173
gpt/gpt.go
Normal file
173
gpt/gpt.go
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
package gpt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Gpt3 struct {
|
||||||
|
CompletionUrl string
|
||||||
|
Prompt string
|
||||||
|
Model string
|
||||||
|
HomeDir string
|
||||||
|
ApiKeyFile string
|
||||||
|
ApiKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Chat struct {
|
||||||
|
Role string `json:"role"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Gpt3Request struct {
|
||||||
|
Model string `json:"model"`
|
||||||
|
Messages []Chat `json:"messages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Gpt3Response struct {
|
||||||
|
Choices []struct {
|
||||||
|
Message Chat `json:"message"`
|
||||||
|
} `json:"choices"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gpt3 *Gpt3) deleteApiKey() {
|
||||||
|
filePath := gpt3.HomeDir + string(filepath.Separator) + gpt3.ApiKeyFile
|
||||||
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := os.Remove(filePath)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gpt3 *Gpt3) updateApiKey(apiKey string) {
|
||||||
|
filePath := gpt3.HomeDir + string(filepath.Separator) + gpt3.ApiKeyFile
|
||||||
|
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 0644)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
apiKey = strings.TrimSpace(apiKey)
|
||||||
|
_, err = file.WriteString(apiKey)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
gpt3.ApiKey = apiKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gpt3 *Gpt3) storeApiKey(apiKey string) {
|
||||||
|
if apiKey == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filePath := gpt3.HomeDir + string(filepath.Separator) + gpt3.ApiKeyFile
|
||||||
|
file, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
apiKey = strings.TrimSpace(apiKey)
|
||||||
|
_, err = file.WriteString(apiKey)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
gpt3.ApiKey = apiKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gpt3 *Gpt3) loadApiKey() bool {
|
||||||
|
dirSeparator := string(filepath.Separator)
|
||||||
|
apiKeyFile := gpt3.HomeDir + dirSeparator + gpt3.ApiKeyFile
|
||||||
|
if _, err := os.Stat(apiKeyFile); os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
apiKey, err := ioutil.ReadFile(apiKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
gpt3.ApiKey = string(apiKey)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gpt3 *Gpt3) UpdateKey() {
|
||||||
|
var apiKey string
|
||||||
|
fmt.Print("OpenAI API Key: ")
|
||||||
|
fmt.Scanln(&apiKey)
|
||||||
|
gpt3.updateApiKey(apiKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gpt3 *Gpt3) DeleteKey() {
|
||||||
|
var c string
|
||||||
|
fmt.Print("Are you sure you want to delete the API key? (y/N): ")
|
||||||
|
fmt.Scanln(&c)
|
||||||
|
if c == "Y" || c == "y" {
|
||||||
|
gpt3.deleteApiKey()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gpt3 *Gpt3) InitKey() {
|
||||||
|
load := gpt3.loadApiKey()
|
||||||
|
if load {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var apiKey string
|
||||||
|
fmt.Print("OpenAI API Key: ")
|
||||||
|
fmt.Scanln(&apiKey)
|
||||||
|
gpt3.storeApiKey(apiKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gpt3 *Gpt3) Completions(ask string) string {
|
||||||
|
req, err := http.NewRequest("POST", gpt3.CompletionUrl, nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(gpt3.ApiKey))
|
||||||
|
|
||||||
|
messages := []Chat{
|
||||||
|
{"system", gpt3.Prompt},
|
||||||
|
{"user", ask},
|
||||||
|
}
|
||||||
|
payload := Gpt3Request{
|
||||||
|
gpt3.Model,
|
||||||
|
messages,
|
||||||
|
}
|
||||||
|
payloadJson, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
req.Body = ioutil.NopCloser(bytes.NewBuffer(payloadJson))
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
fmt.Println(string(body))
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var res Gpt3Response
|
||||||
|
err = json.Unmarshal(body, &res)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSpace(res.Choices[0].Message.Content)
|
||||||
|
}
|
||||||
58
gpt/gpt_test.go
Normal file
58
gpt/gpt_test.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package gpt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestApiKey(t *testing.T) {
|
||||||
|
gpt3 := Gpt3{
|
||||||
|
ApiKeyFile: ".openai_api_key_test",
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
homeDir string
|
||||||
|
apiKey string
|
||||||
|
expected bool
|
||||||
|
expectedApiKey string
|
||||||
|
}{
|
||||||
|
{".", "", false, ""},
|
||||||
|
{"./", "", false, ""},
|
||||||
|
{".", "the key 123", true, "the key 123"},
|
||||||
|
{".", "the key 123\n", true, "the key 123"},
|
||||||
|
{".", " the key 123 ", true, "the key 123"},
|
||||||
|
{".", " \n\n the key 123 \n\n", true, "the key 123"},
|
||||||
|
}
|
||||||
|
defer gpt3.deleteApiKey()
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
gpt3.HomeDir = test.homeDir
|
||||||
|
gpt3.storeApiKey(test.apiKey)
|
||||||
|
load := gpt3.loadApiKey()
|
||||||
|
gpt3.deleteApiKey()
|
||||||
|
if load != test.expected {
|
||||||
|
t.Error("Expected load to be", test.expected, "got", load)
|
||||||
|
}
|
||||||
|
if gpt3.ApiKey != test.expectedApiKey {
|
||||||
|
t.Error("Expected ApiKey to be", test.expectedApiKey, "got", gpt3.ApiKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test update api key
|
||||||
|
gpt3.HomeDir = "."
|
||||||
|
gpt3.storeApiKey("test")
|
||||||
|
updateTests := []struct {
|
||||||
|
apiKey string
|
||||||
|
expectedApiKey string
|
||||||
|
}{
|
||||||
|
{"the key 123", "the key 123"},
|
||||||
|
{"the key 123\n", "the key 123"},
|
||||||
|
{" the key 123 ", "the key 123"},
|
||||||
|
{" \n\n the key 123 \n\n", "the key 123"},
|
||||||
|
}
|
||||||
|
for _, test := range updateTests {
|
||||||
|
gpt3.updateApiKey(test.apiKey)
|
||||||
|
if gpt3.ApiKey != test.expectedApiKey {
|
||||||
|
t.Error("Expected ApiKey to be", test.expectedApiKey, "got", gpt3.ApiKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
gpt/linux-command-gpt
Executable file
BIN
gpt/linux-command-gpt
Executable file
Binary file not shown.
148
main.go
Normal file
148
main.go
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/asrul/linux-command-gpt/gpt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HOST = "https://api.openai.com/v1/"
|
||||||
|
COMPLETIONS = "chat/completions"
|
||||||
|
MODEL = "gpt-3.5-turbo"
|
||||||
|
PROMPT = "I want you to reply with linux command and nothing else. Do not write explanations."
|
||||||
|
|
||||||
|
// This file is created in the user's home directory
|
||||||
|
// Example: /home/username/.openai_api_key
|
||||||
|
API_KEY_FILE = ".openai_api_key"
|
||||||
|
|
||||||
|
HELP = `
|
||||||
|
|
||||||
|
Usage: gpt3 [options]
|
||||||
|
|
||||||
|
--help output usage information
|
||||||
|
--version output the version number
|
||||||
|
--update-key update the API key
|
||||||
|
--delete-key delete the API key
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
VERSION = "0.1.0"
|
||||||
|
CMD_HELP = 100
|
||||||
|
CMD_VERSION = 101
|
||||||
|
CMD_UPDATE = 102
|
||||||
|
CMD_DELETE = 103
|
||||||
|
CMD_COMPLETION = 110
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleCommand(cmd string) int {
|
||||||
|
if cmd == "" || cmd == "--help" || cmd == "-h" {
|
||||||
|
return CMD_HELP
|
||||||
|
}
|
||||||
|
if cmd == "--version" || cmd == "-v" {
|
||||||
|
return CMD_VERSION
|
||||||
|
}
|
||||||
|
if cmd == "--update-key" || cmd == "-u" {
|
||||||
|
return CMD_UPDATE
|
||||||
|
}
|
||||||
|
if cmd == "--delete-key" || cmd == "-d" {
|
||||||
|
return CMD_DELETE
|
||||||
|
}
|
||||||
|
return CMD_COMPLETION
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
currentUser, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := os.Args
|
||||||
|
cmd := ""
|
||||||
|
if len(args) > 1 {
|
||||||
|
cmd = strings.Join(args[1:], " ")
|
||||||
|
}
|
||||||
|
h := handleCommand(cmd)
|
||||||
|
|
||||||
|
if h == CMD_HELP {
|
||||||
|
fmt.Println(HELP)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h == CMD_VERSION {
|
||||||
|
fmt.Println(VERSION)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gpt3 := gpt.Gpt3{
|
||||||
|
CompletionUrl: HOST + COMPLETIONS,
|
||||||
|
Model: MODEL,
|
||||||
|
Prompt: PROMPT,
|
||||||
|
HomeDir: currentUser.HomeDir,
|
||||||
|
ApiKeyFile: API_KEY_FILE,
|
||||||
|
}
|
||||||
|
|
||||||
|
if h == CMD_UPDATE {
|
||||||
|
gpt3.UpdateKey()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h == CMD_DELETE {
|
||||||
|
gpt3.DeleteKey()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s := 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
gpt3.InitKey()
|
||||||
|
r := gpt3.Completions(cmd)
|
||||||
|
done <- true
|
||||||
|
if r == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c := "Y"
|
||||||
|
elapsed := time.Since(s).Seconds()
|
||||||
|
elapsed = math.Round(elapsed*100) / 100
|
||||||
|
fmt.Printf("Completed in %v seconds\n", elapsed)
|
||||||
|
fmt.Printf("┌%s┐\n", strings.Repeat("─", len(r)+2))
|
||||||
|
fmt.Printf("│ %s │\n", r)
|
||||||
|
fmt.Printf("└%s┘\n", strings.Repeat("─", len(r)+2))
|
||||||
|
fmt.Print("Are you sure you want to execute the command? (Y/n): ")
|
||||||
|
fmt.Scanln(&c)
|
||||||
|
if c != "Y" && c != "y" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cmsplit := strings.Split(r, " ")
|
||||||
|
cm := exec.Command(cmsplit[0], cmsplit[1:]...)
|
||||||
|
out, err := cm.Output()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(string(out))
|
||||||
|
}
|
||||||
33
main_test.go
Normal file
33
main_test.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandleCommand(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
command string
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{"", CMD_HELP},
|
||||||
|
{"--help", CMD_HELP},
|
||||||
|
{"-h", CMD_HELP},
|
||||||
|
{"--version", CMD_VERSION},
|
||||||
|
{"-v", CMD_VERSION},
|
||||||
|
{"--update-key", CMD_UPDATE},
|
||||||
|
{"-u", CMD_UPDATE},
|
||||||
|
{"--delete-key", CMD_DELETE},
|
||||||
|
{"-d", CMD_DELETE},
|
||||||
|
{"random strings", CMD_COMPLETION},
|
||||||
|
{"--test", CMD_COMPLETION},
|
||||||
|
{"-test", CMD_COMPLETION},
|
||||||
|
{"how to extract test.tar.gz", CMD_COMPLETION},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
result := handleCommand(test.command)
|
||||||
|
if result != test.expected {
|
||||||
|
t.Error("Expected", test.expected, "got", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user