diff --git a/cmd/goose/github.go b/cmd/goose/github.go index 05ea632..50bf83c 100644 --- a/cmd/goose/github.go +++ b/cmd/goose/github.go @@ -204,11 +204,22 @@ type prResult struct { // It returns GitHub data immediately and starts Turn API queries in the background (when waitForTurn=false), // or waits for Turn data to complete (when waitForTurn=true). func (app *App) fetchPRsInternal(ctx context.Context, waitForTurn bool) (incoming []PR, outgoing []PR, _ error) { + // Check if we have a client + if app.client == nil { + return nil, nil, fmt.Errorf("no GitHub client available: %s", app.authError) + } + // Use targetUser if specified, otherwise use authenticated user - user := app.currentUser.GetLogin() + user := "" + if app.currentUser != nil { + user = app.currentUser.GetLogin() + } if app.targetUser != "" { user = app.targetUser } + if user == "" { + return nil, nil, errors.New("no user specified and current user not loaded") + } const perPage = 100 opts := &github.SearchOptions{ diff --git a/cmd/goose/main.go b/cmd/goose/main.go index 49d0a8c..6cee8a7 100644 --- a/cmd/goose/main.go +++ b/cmd/goose/main.go @@ -77,10 +77,11 @@ type App struct { previousBlockedPRs map[string]bool targetUser string cacheDir string + authError string + incoming []PR outgoing []PR lastMenuTitles []string pendingTurnResults []TurnResult - incoming []PR updateInterval time.Duration consecutiveFailures int mu sync.RWMutex @@ -93,6 +94,52 @@ type App struct { enableAudioCues bool } +func loadCurrentUser(ctx context.Context, app *App) { + log.Println("Loading current user...") + + if app.client == nil { + log.Println("Skipping user load - no GitHub client available") + return + } + + var user *github.User + err := retry.Do(func() error { + var retryErr error + user, _, retryErr = app.client.Users.Get(ctx, "") + if retryErr != nil { + log.Printf("GitHub Users.Get failed (will retry): %v", retryErr) + return retryErr + } + return nil + }, + retry.Attempts(maxRetries), + retry.DelayType(retry.BackOffDelay), + retry.MaxDelay(maxRetryDelay), + retry.OnRetry(func(n uint, err error) { + log.Printf("GitHub Users.Get retry %d/%d: %v", n+1, maxRetries, err) + }), + retry.Context(ctx), + ) + if err != nil { + log.Printf("Warning: Failed to load current user after %d retries: %v", maxRetries, err) + if app.authError == "" { + app.authError = fmt.Sprintf("Failed to load user: %v", err) + } + return + } + + if user == nil { + log.Print("Warning: GitHub API returned nil user") + return + } + + app.currentUser = user + // Log if we're using a different target user (sanitized) + if app.targetUser != "" && app.targetUser != user.GetLogin() { + log.Printf("Querying PRs for user '%s' instead of authenticated user", sanitizeForLog(app.targetUser)) + } +} + func main() { // Parse command line flags var targetUser string @@ -150,40 +197,13 @@ func main() { log.Println("Initializing GitHub clients...") err = app.initClients(ctx) if err != nil { - log.Fatalf("Failed to initialize clients: %v", err) - } - - log.Println("Loading current user...") - var user *github.User - err = retry.Do(func() error { - var retryErr error - user, _, retryErr = app.client.Users.Get(ctx, "") - if retryErr != nil { - log.Printf("GitHub Users.Get failed (will retry): %v", retryErr) - return retryErr - } - return nil - }, - retry.Attempts(maxRetries), - retry.DelayType(retry.BackOffDelay), - retry.MaxDelay(maxRetryDelay), - retry.OnRetry(func(n uint, err error) { - log.Printf("GitHub Users.Get retry %d/%d: %v", n+1, maxRetries, err) - }), - retry.Context(ctx), - ) - if err != nil { - log.Fatalf("Failed to load current user after %d retries: %v", maxRetries, err) - } - if user == nil { - log.Fatal("GitHub API returned nil user") + log.Printf("Warning: Failed to initialize clients: %v", err) + app.authError = err.Error() + // Continue running with auth error - will show error in UI } - app.currentUser = user - // Log if we're using a different target user (sanitized) - if app.targetUser != "" && app.targetUser != user.GetLogin() { - log.Printf("Querying PRs for user '%s' instead of authenticated user", sanitizeForLog(app.targetUser)) - } + // Load current user if we have a client + loadCurrentUser(ctx, app) log.Println("Starting systray...") // Create a cancellable context for the application @@ -199,16 +219,8 @@ func main() { func (app *App) onReady(ctx context.Context) { log.Println("System tray ready") - systray.SetTitle("Loading PRs...") - // Set tooltip based on whether we're using a custom user - tooltip := "GitHub PR Monitor" - if app.targetUser != "" { - tooltip = fmt.Sprintf("GitHub PR Monitor - @%s", app.targetUser) - } - systray.SetTooltip(tooltip) - - // Set up click handlers + // Set up click handlers first (needed for both success and error states) systray.SetOnClick(func(menu systray.IMenu) { log.Println("Icon clicked") if menu != nil { @@ -227,6 +239,26 @@ func (app *App) onReady(ctx context.Context) { } }) + // Check if we have an auth error + if app.authError != "" { + systray.SetTitle("⚠️") + systray.SetTooltip("GitHub PR Monitor - Authentication Error") + // Create initial error menu + app.rebuildMenu(ctx) + // Clean old cache on startup + app.cleanupOldCache() + return + } + + systray.SetTitle("Loading PRs...") + + // Set tooltip based on whether we're using a custom user + tooltip := "GitHub PR Monitor" + if app.targetUser != "" { + tooltip = fmt.Sprintf("GitHub PR Monitor - @%s", app.targetUser) + } + systray.SetTooltip(tooltip) + // Clean old cache on startup app.cleanupOldCache() diff --git a/cmd/goose/ui.go b/cmd/goose/ui.go index 41fdfe0..71fee49 100644 --- a/cmd/goose/ui.go +++ b/cmd/goose/ui.go @@ -226,6 +226,48 @@ func (app *App) rebuildMenu(ctx context.Context) { // Clear all existing menu items systray.ResetMenu() + // Check for auth error first + if app.authError != "" { + // Show authentication error message + errorTitle := systray.AddMenuItem("⚠️ Authentication Error", "") + errorTitle.Disable() + + systray.AddSeparator() + + // Add error details + errorMsg := systray.AddMenuItem(app.authError, "Click to see setup instructions") + errorMsg.Click(func() { + if err := openURL(ctx, "https://cli.github.com/manual/gh_auth_login"); err != nil { + log.Printf("failed to open setup instructions: %v", err) + } + }) + + systray.AddSeparator() + + // Add setup instructions + setupInstr := systray.AddMenuItem("To fix this issue:", "") + setupInstr.Disable() + + option1 := systray.AddMenuItem("1. Install GitHub CLI: brew install gh", "") + option1.Disable() + + option2 := systray.AddMenuItem("2. Run: gh auth login", "") + option2.Disable() + + option3 := systray.AddMenuItem("3. Or set GITHUB_TOKEN environment variable", "") + option3.Disable() + + systray.AddSeparator() + + // Add quit option + quitItem := systray.AddMenuItem("Quit", "") + quitItem.Click(func() { + systray.Quit() + }) + + return + } + // Update tray title app.setTrayTitle()