package main import ( "context" "log" "os" "path/filepath" "git.schreifuchs.ch/schreifuchs/pierre-bot/internal/chatter" "git.schreifuchs.ch/schreifuchs/pierre-bot/internal/gitadapters/bitbucket" "git.schreifuchs.ch/schreifuchs/pierre-bot/internal/gitadapters/gitea" "git.schreifuchs.ch/schreifuchs/pierre-bot/internal/pierre" "github.com/alecthomas/kong" kongyaml "github.com/alecthomas/kong-yaml" ) type BitbucketConfig struct { BaseURL string `help:"Bitbucket Base URL (e.g. https://bitbucket.example.com)" env:"BITBUCKET_URL"` Token string `help:"Bearer Token" env:"BITBUCKET_TOKEN"` } type GiteaConfig struct { BaseURL string `help:"Gitea Base URL (e.g. https://gitea.com)" env:"GITEA_URL"` Token string `help:"API Token" env:"GITEA_TOKEN"` } type RepoArgs struct { Owner string `arg:"" help:"Project Key or Owner" env:"PIERRE_OWNER"` Repo string `arg:"" help:"Repository Slug" env:"PIERRE_REPO"` PRID int `arg:"" help:"Pull Request ID" name:"pr"` } type LLMConfig struct { Provider string `help:"Provider for llm (ollama or gemini)" required:"" env:"LLM_PROVIDER"` BaseURL string `help:"Endpoint for provider (only for ollama)" env:"LLM_BASE_URL"` APIKey string `help:"APIKey for provider" env:"LLM_API_KEY"` Model string `help:"Model to use" env:"LLM_MODEL"` } type Config struct { GitProvider string `help:"Git provider (bitbucket or gitea)" env:"GIT_PROVIDER"` Bitbucket BitbucketConfig `embed:"" prefix:"bitbucket-"` Gitea GiteaConfig `embed:"" prefix:"gitea-"` Repo RepoArgs `embed:""` LLM LLMConfig `embed:"" prefix:"llm-"` Config kong.ConfigFlag `help:"Path to a YAML config file"` } func main() { cfg := &Config{} home, err := os.UserHomeDir() if err != nil { log.Fatalf("could not find home directory: %v", err) } defaultConfig := filepath.Join(home, ".config", "pierre", "config.yaml") // Parse flags, env vars, and config files kong.Parse(cfg, kong.Name("pierre"), kong.Description("AI-powered Pull Request reviewer"), kong.UsageOnError(), kong.Configuration(kongyaml.Loader, "config.yaml", defaultConfig), ) // Auto-detect provider provider := cfg.GitProvider if provider == "" { if cfg.Bitbucket.BaseURL != "" && cfg.Gitea.BaseURL == "" { provider = "bitbucket" } else if cfg.Gitea.BaseURL != "" && cfg.Bitbucket.BaseURL == "" { provider = "gitea" } else if cfg.Bitbucket.BaseURL != "" && cfg.Gitea.BaseURL != "" { log.Fatal("Multiple git providers configured. Please specify one using --git-provider.") } else { log.Fatal("No git provider configured. Please provide Bitbucket or Gitea configuration.") } } var git pierre.GitAdapter switch provider { case "bitbucket": if cfg.Bitbucket.BaseURL == "" { log.Fatal("Bitbucket Base URL is required when using bitbucket provider.") } git = bitbucket.NewBitbucket(cfg.Bitbucket.BaseURL, cfg.Bitbucket.Token) case "gitea": if cfg.Gitea.BaseURL == "" { log.Fatal("Gitea Base URL is required when using gitea provider.") } var err error git, err = gitea.New(cfg.Gitea.BaseURL, cfg.Gitea.Token) if err != nil { log.Fatalf("Error initializing Gitea adapter: %v", err) } default: log.Fatalf("Unknown git provider: %s", provider) } // Initialize AI Adapter var ai pierre.ChatAdapter switch cfg.LLM.Provider { case "gemini": ai, err = chatter.NewGeminiAdapter(context.Background(), cfg.LLM.APIKey, cfg.LLM.Model) case "ollama": ai, err = chatter.NewOllamaAdapter(cfg.LLM.BaseURL, cfg.LLM.Model) case "openai": ai = chatter.NewOpenAIAdapter(cfg.LLM.APIKey, cfg.LLM.Model, cfg.LLM.BaseURL) default: log.Fatalf("%s is not a valid llm provider", cfg.LLM.Provider) } if err != nil { log.Fatalf("Error initializing AI: %v", err) } pierreService := pierre.New(ai, git) if err := pierreService.MakeReview(context.Background(), cfg.Repo.Owner, cfg.Repo.Repo, cfg.Repo.PRID); err != nil { log.Fatalf("Error during review: %v", err) } }