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 cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func runInteractiveConfig() {
// Dynamically generate available models for OpenAI
availableModels := map[string][]string{
"openai": {},
"copilot": {"gpt-5-mini"}, // TODO: update if copilot models are dynamic
"copilot": {"openai/gpt-5-mini"}, // TODO: update if copilot models are dynamic
}

modelDisplayToID := map[string]string{}
Expand Down
43 changes: 24 additions & 19 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/url"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
Expand All @@ -13,8 +14,8 @@ import (
)

type ProviderConfig struct {
APIKey string `mapstructure:"api_key"`
Model string `mapstructure:"model"`
APIKey string `mapstructure:"api_key"`
Model string `mapstructure:"model"`
EndpointURL string `mapstructure:"endpoint_url"`
}

Expand All @@ -37,7 +38,7 @@ func InitConfig() {
viper.SetDefault("providers.copilot.model", "openai/gpt-5-mini")
} else {
viper.SetDefault("active_provider", "openai")
viper.SetDefault("providers.openai.model", "gpt-5-mini")
viper.SetDefault("providers.openai.model", "openai/gpt-5-mini")
}

viper.AutomaticEnv()
Expand Down Expand Up @@ -90,9 +91,6 @@ func GetAPIKey() (string, error) {
if cfg == nil {
InitConfig()
}
if cfg.ActiveProvider == "copilot" {
return LoadGitHubToken()
}

providerConfig, err := GetActiveProviderConfig()
if err != nil {
Expand Down Expand Up @@ -213,12 +211,9 @@ func SetEndpoint(provider, endpoint string) error {
}

func LoadGitHubToken() (string, error) {
if token := os.Getenv("GITHUB_TOKEN"); token != "" {
return token, nil
}

if token := os.Getenv("GITHUB_MODELS_TOKEN"); token != "" {
return token, nil
tok, err := tryGetTokenFromGHCLI()
if err == nil && tok != "" {
return tok, nil
}
Comment on lines +214 to 217

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This change removes the logic for loading the GitHub token from GITHUB_TOKEN or GITHUB_MODELS_TOKEN environment variables. This is a regression in functionality, as users who previously relied on these environment variables will find that they no longer work. It's a common and good practice to check for environment variables before falling back to other methods like executing an external command, as it's faster and provides a conventional configuration method.

    if token := os.Getenv("GITHUB_TOKEN"); token != "" {
		return token, nil
	}
	if token := os.Getenv("GITHUB_MODELS_TOKEN"); token != "" {
		return token, nil
	}
	tok, err := tryGetTokenFromGHCLI()
	if err == nil && tok != "" {
		return tok, nil
	}


configDir := getConfigDir()
Expand Down Expand Up @@ -248,20 +243,30 @@ func LoadGitHubToken() (string, error) {
}
}

return "", fmt.Errorf("GitHub token not found. Please set GITHUB_TOKEN or GITHUB_MODELS_TOKEN environment variable with a Personal Access Token that has 'models' scope")
return "", fmt.Errorf("GitHub token not found via 'gh auth token'; run 'gh auth login' to authenticate the GitHub CLI")
}
func tryGetTokenFromGHCLI() (string, error) {
out, err := exec.Command("gh", "auth", "token").Output()
if err != nil {
return "", err
}
tok := strings.TrimSpace(string(out))
if tok == "" {
return "", fmt.Errorf("gh returned empty token")
}
return tok, nil
}

func getConfigDir() string {
if xdgConfig := os.Getenv("XDG_CONFIG_HOME"); xdgConfig != "" {
return xdgConfig

// WARNING: The code is not woking
} else if runtime.GOOS == "windows" {
if localAppData := os.Getenv("LOCALAPPDATA"); localAppData != "" {
return localAppData
} else {
return filepath.Join(os.Getenv("HOME"), "AppData", "Local")
homeDir, err := os.UserHomeDir()
if err != nil {
fmt.Println("Error getting user home directory:", err)
os.Exit(1)
}
Comment on lines +265 to 268

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Calling os.Exit(1) within a library function like getConfigDir is a critical issue, as it will terminate the entire application if it fails to determine the user's home directory. This makes the package non-reusable and difficult to test. The function should handle this error gracefully instead of exiting. For instance, it could log the error to stderr and fall back to a sensible default, such as the current directory.

		if err != nil {
			fmt.Fprintf(os.Stderr, "Warning: could not determine user home directory: %v. Using current directory for config.\n", err)
			return "."
		}

return filepath.Join(homeDir, "AppData", "Local")
} else {
return filepath.Join(os.Getenv("HOME"), ".config")
}
Expand Down
39 changes: 1 addition & 38 deletions internal/provider/copilot.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package provider

import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
Expand Down Expand Up @@ -59,39 +58,6 @@ func normalizeCopilotModel(model string) string {
return m
}

func (c *CopilotProvider) exchangeGitHubToken(ctx context.Context, githubToken string) (string, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.github.com/copilot_internal/v2/token", nil)
if err != nil {
return "", fmt.Errorf("failed creating token request: %w", err)
}
req.Header.Set("Authorization", "Token "+githubToken)
req.Header.Set("User-Agent", "lazycommit/1.0")

resp, err := c.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("failed exchanging token: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
var body struct {
Message string `json:"message"`
}
_ = json.NewDecoder(resp.Body).Decode(&body)
return "", fmt.Errorf("token exchange failed: %d %s", resp.StatusCode, body.Message)
}
var tr struct {
Token string `json:"token"`
ExpiresAt int64 `json:"expires_at"`
}
if err := json.NewDecoder(resp.Body).Decode(&tr); err != nil {
return "", fmt.Errorf("failed decoding token response: %w", err)
}
if tr.Token == "" {
return "", fmt.Errorf("empty copilot bearer token")
}
return tr.Token, nil
}

func (c *CopilotProvider) getGitHubToken() string {
if c.apiKey != "" {
return c.apiKey
Expand Down Expand Up @@ -122,10 +88,7 @@ func (c *CopilotProvider) GenerateCommitMessages(ctx context.Context, diff strin
return nil, fmt.Errorf("GitHub token is required for Copilot provider")
}

bearer, err := c.exchangeGitHubToken(ctx, githubToken)
if err != nil {
return nil, err
}
bearer := githubToken

client := openai.NewClient(
option.WithBaseURL(c.endpoint),
Expand Down