Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
140 changes: 140 additions & 0 deletions cmd/cli/createMsg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package cmd

import (
"log"
"os"

"github.com/atotto/clipboard"
"github.com/dfanso/commit-msg/cmd/cli/store"
"github.com/dfanso/commit-msg/internal/chatgpt"
"github.com/dfanso/commit-msg/internal/claude"
"github.com/dfanso/commit-msg/internal/display"
"github.com/dfanso/commit-msg/internal/gemini"
"github.com/dfanso/commit-msg/internal/git"
"github.com/dfanso/commit-msg/internal/grok"
"github.com/dfanso/commit-msg/internal/stats"
"github.com/dfanso/commit-msg/pkg/types"
"github.com/pterm/pterm"
)


func CreateCommitMsg () {

// Validate COMMIT_LLM and required API keys
useLLM,err := store.DefaultLLMKey()
if err != nil {
log.Fatal(err)
}

commitLLM := useLLM.LLM
apiKey := useLLM.APIKey


// Get current directory
currentDir, err := os.Getwd()
if err != nil {
log.Fatalf("Failed to get current directory: %v", err)
}

// Check if current directory is a git repository
if !git.IsRepository(currentDir) {
log.Fatalf("Current directory is not a Git repository: %s", currentDir)
}

// Create a minimal config for the API
config := &types.Config{
GrokAPI: "https://api.x.ai/v1/chat/completions",
}

// Create a repo config for the current directory
repoConfig := types.RepoConfig{
Path: currentDir,
}

// Get file statistics before fetching changes
fileStats, err := stats.GetFileStatistics(&repoConfig)
if err != nil {
log.Fatalf("Failed to get file statistics: %v", err)
}

// Display header
pterm.DefaultHeader.WithFullWidth().
WithBackgroundStyle(pterm.NewStyle(pterm.BgDarkGray)).
WithTextStyle(pterm.NewStyle(pterm.FgLightWhite)).
Println("🚀 Commit Message Generator")

pterm.Println()

// Display file statistics with icons
display.ShowFileStatistics(fileStats)

if fileStats.TotalFiles == 0 {
pterm.Warning.Println("No changes detected in the Git repository.")
return
}

// Get the changes
changes, err := git.GetChanges(&repoConfig)
if err != nil {
log.Fatalf("Failed to get Git changes: %v", err)
}

if len(changes) == 0 {
pterm.Warning.Println("No changes detected in the Git repository.")
return
}

pterm.Println()

// Show generating spinner
spinnerGenerating, err := pterm.DefaultSpinner.
WithSequence("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏").
Start("🤖 Generating commit message...")
if err != nil {
log.Fatalf("Failed to start spinner: %v", err)
}

var commitMsg string

switch commitLLM {

case "Gemini":
commitMsg, err = gemini.GenerateCommitMessage(config, changes, apiKey)

case "OpenAI":
commitMsg, err = chatgpt.GenerateCommitMessage(config, changes, apiKey)

case "Claude":
commitMsg, err = claude.GenerateCommitMessage(config, changes, apiKey)

default:
commitMsg, err = grok.GenerateCommitMessage(config, changes, apiKey)
}


if err != nil {
spinnerGenerating.Fail("Failed to generate commit message")
log.Fatalf("Error: %v", err)
}

spinnerGenerating.Success("✅ Commit message generated successfully!")

pterm.Println()

// Display the commit message in a styled panel
display.ShowCommitMessage(commitMsg)

// Copy to clipboard
err = clipboard.WriteAll(commitMsg)
if err != nil {
pterm.Warning.Printf("⚠️ Could not copy to clipboard: %v\n", err)
} else {
pterm.Success.Println("📋 Commit message copied to clipboard!")
}

pterm.Println()

// Display changes preview
display.ShowChangesPreview(fileStats)

}
122 changes: 122 additions & 0 deletions cmd/cli/llmSetup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package cmd

import (
"errors"
"fmt"

"github.com/dfanso/commit-msg/cmd/cli/store"
"github.com/manifoldco/promptui"
)


func SetupLLM() error {

providers := []string{"OpenAI", "Claude", "Gemini", "Grok"}
prompt := promptui.Select{
Label: "Select LLM",
Items: providers,
}

_, model, err := prompt.Run()
if err != nil {
return fmt.Errorf("prompt failed")
}

apiKeyPrompt := promptui.Prompt{
Label: "Enter API Key",
Mask: '*',

}

apiKey, err := apiKeyPrompt.Run()
if err != nil {
return fmt.Errorf("failed to read API Key: %w", err)
}

LLMConfig := store.LLMProvider{
LLM: model,
APIKey: apiKey,
}



err = store.Save(LLMConfig)
if err != nil {
return err
}

fmt.Println("LLM model added")
return nil
}

func UpdateLLM() error {

SavedModels, err := store.ListSavedModels()
if err != nil {
return err
}

if len(SavedModels.LLMProviders) == 0 {
return errors.New("no model exists, Please add atleast one model Run: 'commit llm setup'")

}

models := []string{}
options := []string{"Set Default", "Change API Key", "Delete"}

for _, p := range SavedModels.LLMProviders {
models = append(models, p.LLM)
}

prompt := promptui.Select{
Label: "Select from saved models",
Items: models,
}

_,model,err := prompt.Run()
if err != nil {
return err
}


prompt = promptui.Select{
Label: "Select Option",
Items: options,
}
opNo,_,err := prompt.Run()
if err != nil {
return err
}

apiKeyprompt := promptui.Prompt {
Label: "Enter API Key",
}


switch opNo {
case 0:
err := store.ChangeDefault(model)
if err != nil {
return err
}
fmt.Printf("%s set as default", model)
case 1:
apiKey, err := apiKeyprompt.Run()
if err != nil {
return err
}
err = store.UpdateAPIKey(model, apiKey)
if err != nil {
return err
}
fmt.Printf("%s API Key Updated", model)
case 2:
err := store.DeleteModel(model)
if err != nil {
return err
}
fmt.Printf("%s model deleted", model)
}

return nil
}
76 changes: 76 additions & 0 deletions cmd/cli/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
"os"

"github.com/spf13/cobra"
)

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "commit",
Short: "CLI tool to write commit message",
Long: `Write a commit message with AI of your choice`,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}

var llmCmd = &cobra.Command{
Use: "llm",
Short: "Manage LLM configuration",
}

var llmSetupCmd = &cobra.Command{
Use: "setup",
Short: "Setup your LLM provider and API key",
RunE: func(cmd *cobra.Command, args []string) error {
return SetupLLM()
},
}

var llmUpdateCmd = &cobra.Command{
Use: "update",
Short: "Update or Delete LLM Model",
RunE: func(cmd *cobra.Command, args []string) error {
return UpdateLLM()
},
}

var creatCommitMsg = &cobra.Command{
Use: ".",
Short: "Create Commit Message",
RunE: func(cmd *cobra.Command, args []string) error {
CreateCommitMsg()
return nil
},
}

func init() {
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.

// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.commit-msg.yaml)")

// Cobra also supports local flags, which will only run
// when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
rootCmd.AddCommand(creatCommitMsg)
rootCmd.AddCommand(llmCmd)
llmCmd.AddCommand(llmSetupCmd)
llmCmd.AddCommand(llmUpdateCmd)
}

Loading
Loading