From 601a5bfb9e604e95cddef6f5ec827bb153372b4b Mon Sep 17 00:00:00 2001 From: nitintf Date: Sun, 24 Mar 2024 17:13:56 +0530 Subject: [PATCH] fix: linting --- .golangci.yml | 5 -- cmd/explain.go | 141 +++++++++++++++++++------------------- cmd/navi.go | 163 ++++++++++++++++++++++---------------------- internal/ai/ai.go | 168 +++++++++++++++++++++++----------------------- main.go | 14 ++-- utils/log.go | 38 +++++------ utils/spinner.go | 46 +++++++------ 7 files changed, 285 insertions(+), 290 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 888dd30..ff91cd6 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -14,7 +14,6 @@ linters: - tagliatelle - misspell - depguard - - testifylint - gocritic linters-settings: staticcheck: @@ -39,7 +38,3 @@ linters-settings: gocritic: enabled-checks: - exitAfterDefer - testifylint: - enable-all: true - disable: - - error-is-as # false positive diff --git a/cmd/explain.go b/cmd/explain.go index be86a5d..c360efe 100644 --- a/cmd/explain.go +++ b/cmd/explain.go @@ -1,71 +1,70 @@ -package cmd - -import ( - "github.com/nitintf/navi/internal/ai" - "github.com/nitintf/navi/utils" - "github.com/spf13/cobra" -) - -var explainTemplate = ` -Imagine you are a security-conscious shell or terminal expert with a lot of computer knowledge. - -Your task is to explain the provided shell command in a way that a beginner could understand in less than 80 words. The explanation should: - -* Be clear and concise. -* Avoid technical jargon where possible. -* Not require additional explanation. - -Here is the command: - -> %s - -If the command is not related to shell or terminal, return "NAVI_NAVI_AI_ERROR". - -If the command is unclear or ambiguous, return "NAVI_AI_ERROR". - -If the command requires additional explanation beyond your capabilities, return "NAVI_AI_ERROR". - -If the command is not a shell command, return "NAVI_AI_ERROR". - -**Examples:** - -* Command: "ls" -* Response: "The 'ls' command lists all files and directories in the current directory." - -* Command: "open funny_cat_video.mp4" (Not a shell command) -* Response: "NAVI_AI_ERROR" -` - -var explainCmd = &cobra.Command{ - Use: "explain", - Short: "Explain - Understand your shell commands", - Example: `navi explain "ls"`, - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - command := args[0] - commandLength := len(command) - - if commandLength > 120 { - utils.LogError("Command is too long. Please keep it under 120 characters.") - return - } - - spin := utils.GetSpinner() - spin.Suffix = " Explaining command..." - spin.Start() - resp, err := ai.Generate(cmd.Context(), explainTemplate, command) - - if err != nil { - spin.Stop() - utils.LogError(err.Error()) - return - } - - spin.Stop() - utils.LogExplanation(resp) - }, -} - -func init() { - rootCmd.AddCommand(explainCmd) -} +package cmd + +import ( + "github.com/nitintf/navi/internal/ai" + "github.com/nitintf/navi/utils" + "github.com/spf13/cobra" +) + +var explainTemplate = ` +Imagine you are a security-conscious shell or terminal expert with a lot of computer knowledge. + +Your task is to explain the provided shell command in a way that a beginner could understand in less than 80 words. The explanation should: + +* Be clear and concise. +* Avoid technical jargon where possible. +* Not require additional explanation. + +Here is the command: + +> %s + +If the command is not related to shell or terminal, return "NAVI_NAVI_AI_ERROR". + +If the command is unclear or ambiguous, return "NAVI_AI_ERROR". + +If the command requires additional explanation beyond your capabilities, return "NAVI_AI_ERROR". + +If the command is not a shell command, return "NAVI_AI_ERROR". + +**Examples:** + +* Command: "ls" +* Response: "The 'ls' command lists all files and directories in the current directory." + +* Command: "open funny_cat_video.mp4" (Not a shell command) +* Response: "NAVI_AI_ERROR" +` + +var explainCmd = &cobra.Command{ + Use: "explain", + Short: "Explain - Understand your shell commands", + Example: `navi explain "ls"`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + command := args[0] + commandLength := len(command) + + if commandLength > 120 { + utils.LogError("Command is too long. Please keep it under 120 characters.") + return + } + + spin := utils.GetSpinner() + spin.Suffix = " Explaining command..." + spin.Start() + resp, err := ai.Generate(cmd.Context(), explainTemplate, command) + if err != nil { + spin.Stop() + utils.LogError(err.Error()) + return + } + + spin.Stop() + utils.LogExplanation(resp) + }, +} + +func init() { + rootCmd.AddCommand(explainCmd) +} diff --git a/cmd/navi.go b/cmd/navi.go index 76db6f0..2e38e68 100644 --- a/cmd/navi.go +++ b/cmd/navi.go @@ -1,82 +1,81 @@ -package cmd - -import ( - "fmt" - - "github.com/nitintf/navi/internal/ai" - "github.com/nitintf/navi/utils" - "github.com/spf13/cobra" // Import Cobra library -) - -var commandTemplate = ` -Imagine you are a security-conscious shell or terminal expert with a lot of computer knowledge. - -Write a single, safe shell command that achieves the desired outcome. The command should: - -* Not modify or delete files or folders. -* Be appropriate for a general audience (avoid offensive or harmful commands). -* Not require additional explanation. - -Here is the prompt: - -> %s - -If the prompt is not related to a safe shell command or is not related to shell or commands or terminal, return "NAVI_AI_ERROR". - -If the prompt is not appropriate for a general audience, return "NAVI_AI_ERROR". - -If the prompt is unclear or ambiguous, return "NAVI_AI_ERROR". - -If the prompt requires additional explanation, return "NAVI_AI_ERROR". - -If the prompt is not a shell command, return "NAVI_AI_ERROR". - -If the prompt is a shell command but is not safe, return "NAVI_AI_ERROR". - -**Examples:** - -* Prompt: "List all files in the current directory." -* Response: ls - -* Prompt: "Delete all files in the current directory." (Unsafe) -* Response: "NAVI_AI_ERROR" - -* Prompt: "Show a funny cat video." (Not a shell command) -* Response: "NAVI_AI_ERROR" -` - -var rootCmd = &cobra.Command{ - Use: "navi", - Short: "Navi - Your AI-powered Shell Guide", - Example: `navi "List all files in the current directory."`, - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - prompt := args[0] - promptLength := len(prompt) - - if promptLength > 120 { - utils.LogError("Prompt is too long. Please keep it under 120 characters.") - return - } - - spin := utils.GetSpinner() - spin.Suffix = " Generating command..." - spin.Start() - resp, err := ai.Generate(cmd.Context(), commandTemplate, prompt) - - if err != nil { - spin.Stop() - utils.LogError(err.Error()) - return - } - - spin.Stop() - utils.LogInfo(resp) - }, -} - -func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - } -} +package cmd + +import ( + "fmt" + + "github.com/nitintf/navi/internal/ai" + "github.com/nitintf/navi/utils" + "github.com/spf13/cobra" // Import Cobra library +) + +var commandTemplate = ` +Imagine you are a security-conscious shell or terminal expert with a lot of computer knowledge. + +Write a single, safe shell command that achieves the desired outcome. The command should: + +* Not modify or delete files or folders. +* Be appropriate for a general audience (avoid offensive or harmful commands). +* Not require additional explanation. + +Here is the prompt: + +> %s + +If the prompt is not related to a safe shell command or is not related to shell or commands or terminal, return "NAVI_AI_ERROR". + +If the prompt is not appropriate for a general audience, return "NAVI_AI_ERROR". + +If the prompt is unclear or ambiguous, return "NAVI_AI_ERROR". + +If the prompt requires additional explanation, return "NAVI_AI_ERROR". + +If the prompt is not a shell command, return "NAVI_AI_ERROR". + +If the prompt is a shell command but is not safe, return "NAVI_AI_ERROR". + +**Examples:** + +* Prompt: "List all files in the current directory." +* Response: ls + +* Prompt: "Delete all files in the current directory." (Unsafe) +* Response: "NAVI_AI_ERROR" + +* Prompt: "Show a funny cat video." (Not a shell command) +* Response: "NAVI_AI_ERROR" +` + +var rootCmd = &cobra.Command{ + Use: "navi", + Short: "Navi - Your AI-powered Shell Guide", + Example: `navi "List all files in the current directory."`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + prompt := args[0] + promptLength := len(prompt) + + if promptLength > 120 { + utils.LogError("Prompt is too long. Please keep it under 120 characters.") + return + } + + spin := utils.GetSpinner() + spin.Suffix = " Generating command..." + spin.Start() + resp, err := ai.Generate(cmd.Context(), commandTemplate, prompt) + if err != nil { + spin.Stop() + utils.LogError(err.Error()) + return + } + + spin.Stop() + utils.LogInfo(resp) + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + } +} diff --git a/internal/ai/ai.go b/internal/ai/ai.go index 65f8023..925da46 100644 --- a/internal/ai/ai.go +++ b/internal/ai/ai.go @@ -1,85 +1,83 @@ -package ai - -import ( - "context" - "errors" - "fmt" - "log" - "os" - "strings" - "sync" - - "github.com/google/generative-ai-go/genai" - "google.golang.org/api/option" -) - -var ( - client *genai.Client // Pointer to hold the Gemini client instance - once sync.Once // Once object for ensuring single client creation -) - -func NewClient() (*genai.Client, error) { - once.Do(func() { - ctx := context.Background() - - apiKey := os.Getenv("GEMINI_API_KEY") - - if apiKey == "" { - log.Fatal("GEMINI_API_KEY not set") - } - - var err error - client, err = genai.NewClient(ctx, option.WithAPIKey(apiKey)) - if err != nil { - log.Fatal(err) - } - }) - return client, nil -} - -func Generate(ctx context.Context, template string, prompt string) (string, error) { - client, err := NewClient() - if err != nil { - return "", err - } - - model := client.GenerativeModel("gemini-pro") - - userPrompt := fmt.Sprintf(template, prompt) - - resp, err := model.GenerateContent(ctx, genai.Text(userPrompt)) - - if err != nil { - return "", err - } - - part := getResponse(resp) - - if part == nil { - return "", errors.New("please provide a valid prompt") - } - - return fmt.Sprint(part), nil -} - -func getResponse(resp *genai.GenerateContentResponse) genai.Part { - var foundPart genai.Part - - for _, cand := range resp.Candidates { - if cand.Content != nil { - for _, part := range cand.Content.Parts { - if strings.Contains(fmt.Sprint(part), "NAVI_AI_ERROR") { - return nil - } - foundPart = part - } - } - } - - if foundPart == nil { - return nil - } - - return foundPart - -} +package ai + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "strings" + "sync" + + "github.com/google/generative-ai-go/genai" + "google.golang.org/api/option" +) + +var ( + client *genai.Client // Pointer to hold the Gemini client instance + once sync.Once // Once object for ensuring single client creation +) + +func NewClient() (*genai.Client, error) { + once.Do(func() { + ctx := context.Background() + + apiKey := os.Getenv("GEMINI_API_KEY") + + if apiKey == "" { + log.Fatal("GEMINI_API_KEY not set") + } + + var err error + client, err = genai.NewClient(ctx, option.WithAPIKey(apiKey)) + if err != nil { + log.Fatal(err) + } + }) + return client, nil +} + +func Generate(ctx context.Context, template string, prompt string) (string, error) { + client, err := NewClient() + if err != nil { + return "", err + } + + model := client.GenerativeModel("gemini-pro") + + userPrompt := fmt.Sprintf(template, prompt) + + resp, err := model.GenerateContent(ctx, genai.Text(userPrompt)) + if err != nil { + return "", err + } + + part := getResponse(resp) + + if part == nil { + return "", errors.New("please provide a valid prompt") + } + + return fmt.Sprint(part), nil +} + +func getResponse(resp *genai.GenerateContentResponse) genai.Part { + var foundPart genai.Part + + for _, cand := range resp.Candidates { + if cand.Content != nil { + for _, part := range cand.Content.Parts { + if strings.Contains(fmt.Sprint(part), "NAVI_AI_ERROR") { + return nil + } + foundPart = part + } + } + } + + if foundPart == nil { + return nil + } + + return foundPart +} diff --git a/main.go b/main.go index 65b19a3..d8a87ca 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,7 @@ -package main - -import "github.com/nitintf/navi/cmd" - -func main() { - cmd.Execute() -} +package main + +import "github.com/nitintf/navi/cmd" + +func main() { + cmd.Execute() +} diff --git a/utils/log.go b/utils/log.go index 8d99a28..ebfe420 100644 --- a/utils/log.go +++ b/utils/log.go @@ -1,19 +1,19 @@ -package utils - -import "fmt" - -func LogSuccess(message string) { - fmt.Printf("\033[1;32m%s\033[0m\n", message) -} - -func LogError(message string) { - fmt.Printf("\033[1;31m%s\033[0m\n", message) -} - -func LogInfo(message string) { - fmt.Printf("\n\033[1;36m%s\033[0m%s\n", ">> ", message) -} - -func LogExplanation(message string) { - fmt.Printf("\n\033[1;33m%s\033[0m\n\n%s\n", "Explanation >> ", message) -} +package utils + +import "fmt" + +func LogSuccess(message string) { + fmt.Printf("\033[1;32m%s\033[0m\n", message) +} + +func LogError(message string) { + fmt.Printf("\033[1;31m%s\033[0m\n", message) +} + +func LogInfo(message string) { + fmt.Printf("\n\033[1;36m%s\033[0m%s\n", ">> ", message) +} + +func LogExplanation(message string) { + fmt.Printf("\n\033[1;33m%s\033[0m\n\n%s\n", "Explanation >> ", message) +} diff --git a/utils/spinner.go b/utils/spinner.go index e5af812..db5aeed 100644 --- a/utils/spinner.go +++ b/utils/spinner.go @@ -1,21 +1,25 @@ -package utils - -import ( - "sync" - "time" - - "github.com/briandowns/spinner" -) - -var ( - spin *spinner.Spinner - once sync.Once -) - -func GetSpinner() *spinner.Spinner { - once.Do(func() { - spin = spinner.New(spinner.CharSets[14], 100*time.Millisecond) - spin.Color("cyan") - }) - return spin -} +package utils + +import ( + "log" + "sync" + "time" + + "github.com/briandowns/spinner" +) + +var ( + spin *spinner.Spinner + once sync.Once +) + +func GetSpinner() *spinner.Spinner { + once.Do(func() { + spin = spinner.New(spinner.CharSets[14], 100*time.Millisecond) + err := spin.Color("cyan") + if err != nil { + log.Fatal(err) + } + }) + return spin +}