@@ -12,12 +12,15 @@ import (
12 "github.com/atotto/clipboard"
12 "github.com/atotto/clipboard"
13 "github.com/charmbracelet/catwalk/pkg/catwalk"
13 "github.com/charmbracelet/catwalk/pkg/catwalk"
14 "github.com/charmbracelet/crush/internal/agent"
14 "github.com/charmbracelet/crush/internal/agent"
15+ hyperp "github.com/charmbracelet/crush/internal/agent/hyper"
15 "github.com/charmbracelet/crush/internal/config"
16 "github.com/charmbracelet/crush/internal/config"
16 "github.com/charmbracelet/crush/internal/home"
17 "github.com/charmbracelet/crush/internal/home"
17 "github.com/charmbracelet/crush/internal/tui/components/chat"
18 "github.com/charmbracelet/crush/internal/tui/components/chat"
18 "github.com/charmbracelet/crush/internal/tui/components/core"
19 "github.com/charmbracelet/crush/internal/tui/components/core"
19 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
20 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
20 "github.com/charmbracelet/crush/internal/tui/components/dialogs/claude"
21 "github.com/charmbracelet/crush/internal/tui/components/dialogs/claude"
22+ "github.com/charmbracelet/crush/internal/tui/components/dialogs/copilot"
23+ "github.com/charmbracelet/crush/internal/tui/components/dialogs/hyper"
21 "github.com/charmbracelet/crush/internal/tui/components/dialogs/models"
24 "github.com/charmbracelet/crush/internal/tui/components/dialogs/models"
22 "github.com/charmbracelet/crush/internal/tui/components/logo"
25 "github.com/charmbracelet/crush/internal/tui/components/logo"
23 lspcomponent "github.com/charmbracelet/crush/internal/tui/components/lsp"
26 lspcomponent "github.com/charmbracelet/crush/internal/tui/components/lsp"
@@ -55,6 +58,12 @@ type Splash interface {
56 // IsClaudeOAuthComplete returns whether Claude OAuth flow is complete
59 // IsClaudeOAuthComplete returns whether Claude OAuth flow is complete
57 IsClaudeOAuthComplete() bool
60 IsClaudeOAuthComplete() bool
62+ // IsShowingClaudeOAuth2 returns whether showing Hyper OAuth2 flow
63+ IsShowingHyperOAuth2() bool
65+ // IsShowingClaudeOAuth2 returns whether showing GitHub Copilot OAuth2 flow
66+ IsShowingCopilotOAuth2() bool
@@ -87,6 +96,14 @@ type splashCmp struct {
87 isAPIKeyValid bool
96 isAPIKeyValid bool
88 apiKeyValue string
97 apiKeyValue string
99+ // Hyper device flow state
100+ hyperDeviceFlow *hyper.DeviceFlow
101+ showHyperDeviceFlow bool
103+ // Copilot device flow state
104+ copilotDeviceFlow *copilot.DeviceFlow
105+ showCopilotDeviceFlow bool
90 // Claude state
107 // Claude state
91 claudeAuthMethodChooser *claude.AuthMethodChooser
108 claudeAuthMethodChooser *claude.AuthMethodChooser
92 claudeOAuth2 *claude.OAuth2
109 claudeOAuth2 *claude.OAuth2
@@ -186,6 +203,26 @@ func (s *splashCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
188 return s, tea.Batch(cmds...)
205 return s, tea.Batch(cmds...)
206+ case hyper.DeviceFlowCompletedMsg:
207+ s.showHyperDeviceFlow = false
208+ return s, s.saveAPIKeyAndContinue(msg.Token, true)
209+ case hyper.DeviceAuthInitiatedMsg, hyper.DeviceFlowErrorMsg:
210+ if s.hyperDeviceFlow != nil {
211+ u, cmd := s.hyperDeviceFlow.Update(msg)
212+ s.hyperDeviceFlow = u.(*hyper.DeviceFlow)
216+ case copilot.DeviceAuthInitiatedMsg, copilot.DeviceFlowErrorMsg:
217+ if s.copilotDeviceFlow != nil {
218+ u, cmd := s.copilotDeviceFlow.Update(msg)
219+ s.copilotDeviceFlow = u.(*copilot.DeviceFlow)
223+ case copilot.DeviceFlowCompletedMsg:
224+ s.showCopilotDeviceFlow = false
225+ return s, s.saveAPIKeyAndContinue(msg.Token, true)
189 case claude.AuthenticationCompleteMsg:
226 case claude.AuthenticationCompleteMsg:
190 s.showClaudeAuthMethodChooser = false
227 s.showClaudeAuthMethodChooser = false
191 s.showClaudeOAuth2 = false
228 s.showClaudeOAuth2 = false
@@ -205,41 +242,49 @@ func (s *splashCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
206 case tea.KeyPressMsg:
243 case tea.KeyPressMsg:
208− case key.Matches(msg, s.keyMap.Copy):
245+ case key.Matches(msg, s.keyMap.Copy) && s.showHyperDeviceFlow:
246+ return s, s.hyperDeviceFlow.CopyCode()
247+ case key.Matches(msg, s.keyMap.Copy) && s.showCopilotDeviceFlow:
248+ return s, s.copilotDeviceFlow.CopyCode()
209− if s.showClaudeOAuth2 && s.claudeOAuth2.State == claude.OAuthStateURL {
249+ case key.Matches(msg, s.keyMap.Copy) && s.showClaudeOAuth2 && s.claudeOAuth2.State == claude.OAuthStateURL:
210− return s, tea.Sequence(
250+ return s, tea.Sequence(
211− tea.SetClipboard(s.claudeOAuth2.URL),
251+ tea.SetClipboard(s.claudeOAuth2.URL),
212− func() tea.Msg {
252+ func() tea.Msg {
213− _ = clipboard.WriteAll(s.claudeOAuth2.URL)
253+ _ = clipboard.WriteAll(s.claudeOAuth2.URL)
214− return nil
254+ return nil
216− util.ReportInfo("URL copied to clipboard"),
256+ util.ReportInfo("URL copied to clipboard"),
218− } else if s.showClaudeAuthMethodChooser {
258+ case key.Matches(msg, s.keyMap.Copy) && s.showClaudeAuthMethodChooser:
219− u, cmd := s.claudeAuthMethodChooser.Update(msg)
259+ u, cmd := s.claudeAuthMethodChooser.Update(msg)
220− s.claudeAuthMethodChooser = u.(*claude.AuthMethodChooser)
260+ s.claudeAuthMethodChooser = u.(*claude.AuthMethodChooser)
221− return s, cmd
261+ return s, cmd
222− } else if s.showClaudeOAuth2 {
262+ case key.Matches(msg, s.keyMap.Copy) && s.showClaudeOAuth2:
223− u, cmd := s.claudeOAuth2.Update(msg)
263+ u, cmd := s.claudeOAuth2.Update(msg)
224− s.claudeOAuth2 = u.(*claude.OAuth2)
264+ s.claudeOAuth2 = u.(*claude.OAuth2)
225− return s, cmd
265+ return s, cmd
227 case key.Matches(msg, s.keyMap.Back):
266 case key.Matches(msg, s.keyMap.Back):
228− if s.showClaudeAuthMethodChooser {
268+ case s.showClaudeAuthMethodChooser:
229 s.claudeAuthMethodChooser.SetDefaults()
269 s.claudeAuthMethodChooser.SetDefaults()
230 s.showClaudeAuthMethodChooser = false
270 s.showClaudeAuthMethodChooser = false
231 return s, nil
271 return s, nil
233− if s.showClaudeOAuth2 {
272+ case s.showClaudeOAuth2:
234 s.claudeOAuth2.SetDefaults()
273 s.claudeOAuth2.SetDefaults()
235 s.showClaudeOAuth2 = false
274 s.showClaudeOAuth2 = false
236 s.showClaudeAuthMethodChooser = true
275 s.showClaudeAuthMethodChooser = true
237 return s, nil
276 return s, nil
238− }
277+ case s.showHyperDeviceFlow:
239− if s.isAPIKeyValid {
278+ s.hyperDeviceFlow = nil
279+ s.showHyperDeviceFlow = false
240 return s, nil
280 return s, nil
241− }
281+ case s.showCopilotDeviceFlow:
282+ s.copilotDeviceFlow = nil
283+ s.showCopilotDeviceFlow = false
285+ case s.isAPIKeyValid:
242− if s.needsAPIKey {
287+ case s.needsAPIKey:
243 if s.selectedModel.Provider.ID == catwalk.InferenceProviderAnthropic {
288 if s.selectedModel.Provider.ID == catwalk.InferenceProviderAnthropic {
244 s.showClaudeAuthMethodChooser = true
289 s.showClaudeAuthMethodChooser = true
@@ -251,7 +296,8 @@ func (s *splashCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
251 return s, nil
296 return s, nil
253 case key.Matches(msg, s.keyMap.Select):
298 case key.Matches(msg, s.keyMap.Select):
254− if s.showClaudeAuthMethodChooser {
300+ case s.showClaudeAuthMethodChooser:
255 selectedItem := s.modelList.SelectedModel()
301 selectedItem := s.modelList.SelectedModel()
256 if selectedItem == nil {
302 if selectedItem == nil {
257 return s, nil
303 return s, nil
@@ -269,16 +315,17 @@ func (s *splashCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
269 s.showClaudeOAuth2 = true
315 s.showClaudeOAuth2 = true
271 return s, nil
317 return s, nil
273− if s.showClaudeOAuth2 {
318+ case s.showClaudeOAuth2:
274 m2, cmd2 := s.claudeOAuth2.ValidationConfirm()
319 m2, cmd2 := s.claudeOAuth2.ValidationConfirm()
275 s.claudeOAuth2 = m2.(*claude.OAuth2)
320 s.claudeOAuth2 = m2.(*claude.OAuth2)
276 return s, cmd2
321 return s, cmd2
277− }
322+ case s.showHyperDeviceFlow:
323+ return s, s.hyperDeviceFlow.CopyCodeAndOpenURL()
324+ case s.showCopilotDeviceFlow:
325+ return s, s.copilotDeviceFlow.CopyCodeAndOpenURL()
278− if s.isAPIKeyValid {
326+ case s.isAPIKeyValid:
279 return s, s.saveAPIKeyAndContinue(s.apiKeyValue, true)
327 return s, s.saveAPIKeyAndContinue(s.apiKeyValue, true)
281− if s.isOnboarding && !s.needsAPIKey {
328+ case s.isOnboarding && !s.needsAPIKey:
282 selectedItem := s.modelList.SelectedModel()
329 selectedItem := s.modelList.SelectedModel()
283 if selectedItem == nil {
330 if selectedItem == nil {
284 return s, nil
331 return s, nil
@@ -288,9 +335,26 @@ func (s *splashCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
288 s.isOnboarding = false
335 s.isOnboarding = false
289 return s, tea.Batch(cmd, util.CmdHandler(OnboardingCompleteMsg{}))
336 return s, tea.Batch(cmd, util.CmdHandler(OnboardingCompleteMsg{}))
338+ switch selectedItem.Provider.ID {
291− if selectedItem.Provider.ID == catwalk.InferenceProviderAnthropic {
339+ case catwalk.InferenceProviderAnthropic:
292 s.showClaudeAuthMethodChooser = true
340 s.showClaudeAuthMethodChooser = true
293 return s, nil
341 return s, nil
343+ s.selectedModel = selectedItem
344+ s.showHyperDeviceFlow = true
345+ s.hyperDeviceFlow = hyper.NewDeviceFlow()
346+ s.hyperDeviceFlow.SetWidth(min(s.width-2, 60))
347+ return s, s.hyperDeviceFlow.Init()
348+ case catwalk.InferenceProviderCopilot:
349+ if token, ok := config.Get().ImportCopilot(); ok {
350+ s.selectedModel = selectedItem
351+ return s, s.saveAPIKeyAndContinue(token, true)
353+ s.selectedModel = selectedItem
354+ s.showCopilotDeviceFlow = true
355+ s.copilotDeviceFlow = copilot.NewDeviceFlow()
356+ s.copilotDeviceFlow.SetWidth(min(s.width-2, 60))
357+ return s, s.copilotDeviceFlow.Init()
295 // Provider not configured, show API key input
359 // Provider not configured, show API key input
296 s.needsAPIKey = true
360 s.needsAPIKey = true
@@ -298,7 +362,7 @@ func (s *splashCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
298 s.apiKeyInput.SetProviderName(selectedItem.Provider.Name)
362 s.apiKeyInput.SetProviderName(selectedItem.Provider.Name)
299 return s, nil
363 return s, nil
301− } else if s.needsAPIKey {
365+ case s.needsAPIKey:
302 // Handle API key submission
366 // Handle API key submission
303 s.apiKeyValue = strings.TrimSpace(s.apiKeyInput.Value())
367 s.apiKeyValue = strings.TrimSpace(s.apiKeyInput.Value())
304 if s.apiKeyValue == "" {
368 if s.apiKeyValue == "" {
@@ -339,7 +403,7 @@ func (s *splashCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
342− } else if s.needsProjectInit {
406+ case s.needsProjectInit:
343 return s, s.initializeProject()
407 return s, s.initializeProject()
345 case key.Matches(msg, s.keyMap.Tab, s.keyMap.LeftRight):
409 case key.Matches(msg, s.keyMap.Tab, s.keyMap.LeftRight):
@@ -387,44 +451,71 @@ func (s *splashCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
387 return s, s.initializeProject()
451 return s, s.initializeProject()
390− if s.showClaudeAuthMethodChooser {
455+ case s.showClaudeAuthMethodChooser:
391 u, cmd := s.claudeAuthMethodChooser.Update(msg)
456 u, cmd := s.claudeAuthMethodChooser.Update(msg)
392 s.claudeAuthMethodChooser = u.(*claude.AuthMethodChooser)
457 s.claudeAuthMethodChooser = u.(*claude.AuthMethodChooser)
393 return s, cmd
458 return s, cmd
394− } else if s.showClaudeOAuth2 {
459+ case s.showClaudeOAuth2:
395 u, cmd := s.claudeOAuth2.Update(msg)
460 u, cmd := s.claudeOAuth2.Update(msg)
396 s.claudeOAuth2 = u.(*claude.OAuth2)
461 s.claudeOAuth2 = u.(*claude.OAuth2)
397 return s, cmd
462 return s, cmd
463+ case s.showHyperDeviceFlow:
464+ u, cmd := s.hyperDeviceFlow.Update(msg)
465+ s.hyperDeviceFlow = u.(*hyper.DeviceFlow)
467+ case s.showCopilotDeviceFlow:
468+ u, cmd := s.copilotDeviceFlow.Update(msg)
469+ s.copilotDeviceFlow = u.(*copilot.DeviceFlow)
398− } else if s.needsAPIKey {
471+ case s.needsAPIKey:
399 u, cmd := s.apiKeyInput.Update(msg)
472 u, cmd := s.apiKeyInput.Update(msg)
400 s.apiKeyInput = u.(*models.APIKeyInput)
473 s.apiKeyInput = u.(*models.APIKeyInput)
401 return s, cmd
474 return s, cmd
402− } else if s.isOnboarding {
475+ case s.isOnboarding:
403 u, cmd := s.modelList.Update(msg)
476 u, cmd := s.modelList.Update(msg)
404 s.modelList = u
477 s.modelList = u
405 return s, cmd
478 return s, cmd
408 case tea.PasteMsg:
481 case tea.PasteMsg:
409− if s.showClaudeOAuth2 {
483+ case s.showClaudeOAuth2:
410 u, cmd := s.claudeOAuth2.Update(msg)
484 u, cmd := s.claudeOAuth2.Update(msg)
411 s.claudeOAuth2 = u.(*claude.OAuth2)
485 s.claudeOAuth2 = u.(*claude.OAuth2)
412 return s, cmd
486 return s, cmd
487+ case s.showHyperDeviceFlow:
488+ u, cmd := s.hyperDeviceFlow.Update(msg)
489+ s.hyperDeviceFlow = u.(*hyper.DeviceFlow)
491+ case s.showCopilotDeviceFlow:
492+ u, cmd := s.copilotDeviceFlow.Update(msg)
493+ s.copilotDeviceFlow = u.(*copilot.DeviceFlow)
413− } else if s.needsAPIKey {
495+ case s.needsAPIKey:
414 u, cmd := s.apiKeyInput.Update(msg)
496 u, cmd := s.apiKeyInput.Update(msg)
415 s.apiKeyInput = u.(*models.APIKeyInput)
497 s.apiKeyInput = u.(*models.APIKeyInput)
416 return s, cmd
498 return s, cmd
417− } else if s.isOnboarding {
499+ case s.isOnboarding:
418 var cmd tea.Cmd
500 var cmd tea.Cmd
419 s.modelList, cmd = s.modelList.Update(msg)
501 s.modelList, cmd = s.modelList.Update(msg)
420 return s, cmd
502 return s, cmd
422 case spinner.TickMsg:
504 case spinner.TickMsg:
423− if s.showClaudeOAuth2 {
506+ case s.showClaudeOAuth2:
424 u, cmd := s.claudeOAuth2.Update(msg)
507 u, cmd := s.claudeOAuth2.Update(msg)
425 s.claudeOAuth2 = u.(*claude.OAuth2)
508 s.claudeOAuth2 = u.(*claude.OAuth2)
426 return s, cmd
509 return s, cmd
427− } else {
510+ case s.showHyperDeviceFlow:
511+ u, cmd := s.hyperDeviceFlow.Update(msg)
512+ s.hyperDeviceFlow = u.(*hyper.DeviceFlow)
514+ case s.showCopilotDeviceFlow:
515+ u, cmd := s.copilotDeviceFlow.Update(msg)
516+ s.copilotDeviceFlow = u.(*copilot.DeviceFlow)
428 u, cmd := s.apiKeyInput.Update(msg)
519 u, cmd := s.apiKeyInput.Update(msg)
429 s.apiKeyInput = u.(*models.APIKeyInput)
520 s.apiKeyInput = u.(*models.APIKeyInput)
430 return s, cmd
521 return s, cmd
@@ -562,8 +653,10 @@ func (s *splashCmp) isProviderConfigured(providerID string) bool {
562func (s *splashCmp) View() string {
653func (s *splashCmp) View() string {
563 t := styles.CurrentTheme()
654 t := styles.CurrentTheme()
564 var content string
655 var content string
565− if s.showClaudeAuthMethodChooser {
658+ case s.showClaudeAuthMethodChooser:
566− remainingHeight := s.height - lipgloss.Height(s.logoRendered) - (SplashScreenPaddingY * 2)
659+ remainingHeight := s.height - lipgloss.Height(s.logoRendered) - SplashScreenPaddingY
567 chooserView := s.claudeAuthMethodChooser.View()
660 chooserView := s.claudeAuthMethodChooser.View()
568 authMethodSelector := t.S().Base.AlignVertical(lipgloss.Bottom).Height(remainingHeight).Render(
661 authMethodSelector := t.S().Base.AlignVertical(lipgloss.Bottom).Height(remainingHeight).Render(
569 lipgloss.JoinVertical(
662 lipgloss.JoinVertical(
@@ -578,8 +671,8 @@ func (s *splashCmp) View() string {
578 s.logoRendered,
671 s.logoRendered,
579 authMethodSelector,
672 authMethodSelector,
581− } else if s.showClaudeOAuth2 {
674+ case s.showClaudeOAuth2:
582− remainingHeight := s.height - lipgloss.Height(s.logoRendered) - (SplashScreenPaddingY * 2)
675+ remainingHeight := s.height - lipgloss.Height(s.logoRendered) - SplashScreenPaddingY
583 oauth2View := s.claudeOAuth2.View()
676 oauth2View := s.claudeOAuth2.View()
584 oauthSelector := t.S().Base.AlignVertical(lipgloss.Bottom).Height(remainingHeight).Render(
677 oauthSelector := t.S().Base.AlignVertical(lipgloss.Bottom).Height(remainingHeight).Render(
585 lipgloss.JoinVertical(
678 lipgloss.JoinVertical(
@@ -594,8 +687,38 @@ func (s *splashCmp) View() string {
594 s.logoRendered,
687 s.logoRendered,
595 oauthSelector,
688 oauthSelector,
690+ case s.showHyperDeviceFlow:
691+ remainingHeight := s.height - lipgloss.Height(s.logoRendered) - SplashScreenPaddingY
692+ hyperView := s.hyperDeviceFlow.View()
693+ hyperSelector := t.S().Base.AlignVertical(lipgloss.Bottom).Height(remainingHeight).Render(
694+ lipgloss.JoinVertical(
696+ t.S().Base.PaddingLeft(1).Foreground(t.Primary).Render("Let's Auth Hyper"),
700+ content = lipgloss.JoinVertical(
705+ case s.showCopilotDeviceFlow:
706+ remainingHeight := s.height - lipgloss.Height(s.logoRendered) - SplashScreenPaddingY
707+ copilotView := s.copilotDeviceFlow.View()
708+ copilotSelector := t.S().Base.AlignVertical(lipgloss.Bottom).Height(remainingHeight).Render(
709+ lipgloss.JoinVertical(
711+ t.S().Base.PaddingLeft(1).Foreground(t.Primary).Render("Let's Auth GitHub Copilot"),
715+ content = lipgloss.JoinVertical(
597− } else if s.needsAPIKey {
720+ case s.needsAPIKey:
598− remainingHeight := s.height - lipgloss.Height(s.logoRendered) - (SplashScreenPaddingY * 2)
721+ remainingHeight := s.height - lipgloss.Height(s.logoRendered) - SplashScreenPaddingY
599 apiKeyView := t.S().Base.PaddingLeft(1).Render(s.apiKeyInput.View())
722 apiKeyView := t.S().Base.PaddingLeft(1).Render(s.apiKeyInput.View())
600 apiKeySelector := t.S().Base.AlignVertical(lipgloss.Bottom).Height(remainingHeight).Render(
723 apiKeySelector := t.S().Base.AlignVertical(lipgloss.Bottom).Height(remainingHeight).Render(
601 lipgloss.JoinVertical(
724 lipgloss.JoinVertical(
@@ -608,9 +731,9 @@ func (s *splashCmp) View() string {
608 s.logoRendered,
731 s.logoRendered,
609 apiKeySelector,
732 apiKeySelector,
611− } else if s.isOnboarding {
734+ case s.isOnboarding:
612 modelListView := s.modelList.View()
735 modelListView := s.modelList.View()
613− remainingHeight := s.height - lipgloss.Height(s.logoRendered) - (SplashScreenPaddingY * 2)
736+ remainingHeight := s.height - lipgloss.Height(s.logoRendered) - SplashScreenPaddingY
614 modelSelector := t.S().Base.AlignVertical(lipgloss.Bottom).Height(remainingHeight).Render(
737 modelSelector := t.S().Base.AlignVertical(lipgloss.Bottom).Height(remainingHeight).Render(
615 lipgloss.JoinVertical(
738 lipgloss.JoinVertical(
616 lipgloss.Left,
739 lipgloss.Left,
@@ -624,7 +747,7 @@ func (s *splashCmp) View() string {
624 s.logoRendered,
747 s.logoRendered,
625 modelSelector,
748 modelSelector,
627− } else if s.needsProjectInit {
750+ case s.needsProjectInit:
628 titleStyle := t.S().Base.Foreground(t.FgBase)
751 titleStyle := t.S().Base.Foreground(t.FgBase)
629 pathStyle := t.S().Base.Foreground(t.Success).PaddingLeft(2)
752 pathStyle := t.S().Base.Foreground(t.Success).PaddingLeft(2)
630 bodyStyle := t.S().Base.Foreground(t.FgMuted)
753 bodyStyle := t.S().Base.Foreground(t.FgMuted)
@@ -675,7 +798,7 @@ func (s *splashCmp) View() string {
676 initContent,
799 initContent,
678− } else {
801+ default:
679 parts := []string{
802 parts := []string{
680 s.logoRendered,
803 s.logoRendered,
681 s.infoSection(),
804 s.infoSection(),
@@ -692,28 +815,25 @@ func (s *splashCmp) View() string {
694func (s *splashCmp) Cursor() *tea.Cursor {
817func (s *splashCmp) Cursor() *tea.Cursor {
695− if s.showClaudeAuthMethodChooser {
819+ case s.showClaudeAuthMethodChooser:
696 return nil
820 return nil
698− if s.showClaudeOAuth2 {
821+ case s.showClaudeOAuth2:
699 if cursor := s.claudeOAuth2.CodeInput.Cursor(); cursor != nil {
822 if cursor := s.claudeOAuth2.CodeInput.Cursor(); cursor != nil {
700 cursor.Y += 2 // FIXME(@andreynering): Why do we need this?
823 cursor.Y += 2 // FIXME(@andreynering): Why do we need this?
701 return s.moveCursor(cursor)
824 return s.moveCursor(cursor)
703 return nil
826 return nil
705− if s.needsAPIKey {
827+ case s.needsAPIKey:
706 cursor := s.apiKeyInput.Cursor()
828 cursor := s.apiKeyInput.Cursor()
707 if cursor != nil {
829 if cursor != nil {
708 return s.moveCursor(cursor)
830 return s.moveCursor(cursor)
710− } else if s.isOnboarding {
832+ case s.isOnboarding:
711 cursor := s.modelList.Cursor()
833 cursor := s.modelList.Cursor()
712 if cursor != nil {
834 if cursor != nil {
713 return s.moveCursor(cursor)
835 return s.moveCursor(cursor)
718 return nil
838 return nil
@@ -805,13 +925,14 @@ func (s *splashCmp) logoGap() int {
806// Bindings implements SplashPage.
926// Bindings implements SplashPage.
807func (s *splashCmp) Bindings() []key.Binding {
927func (s *splashCmp) Bindings() []key.Binding {
808− if s.showClaudeAuthMethodChooser {
929+ case s.showClaudeAuthMethodChooser:
809 return []key.Binding{
930 return []key.Binding{
810 s.keyMap.Select,
931 s.keyMap.Select,
811 s.keyMap.Tab,
932 s.keyMap.Tab,
812 s.keyMap.Back,
933 s.keyMap.Back,
814− } else if s.showClaudeOAuth2 {
935+ case s.showClaudeOAuth2:
815 bindings := []key.Binding{
936 bindings := []key.Binding{
816 s.keyMap.Select,
937 s.keyMap.Select,
@@ -819,18 +940,18 @@ func (s *splashCmp) Bindings() []key.Binding {
819 bindings = append(bindings, s.keyMap.Copy)
940 bindings = append(bindings, s.keyMap.Copy)
821 return bindings
942 return bindings
822− } else if s.needsAPIKey {
943+ case s.needsAPIKey:
823 return []key.Binding{
944 return []key.Binding{
824 s.keyMap.Select,
945 s.keyMap.Select,
825 s.keyMap.Back,
946 s.keyMap.Back,
827− } else if s.isOnboarding {
948+ case s.isOnboarding:
828 return []key.Binding{
949 return []key.Binding{
829 s.keyMap.Select,
950 s.keyMap.Select,
830 s.keyMap.Next,
951 s.keyMap.Next,
831 s.keyMap.Previous,
952 s.keyMap.Previous,
833− } else if s.needsProjectInit {
954+ case s.needsProjectInit:
834 return []key.Binding{
955 return []key.Binding{
835 s.keyMap.Select,
956 s.keyMap.Select,
836 s.keyMap.Yes,
957 s.keyMap.Yes,
@@ -838,8 +959,9 @@ func (s *splashCmp) Bindings() []key.Binding {
838 s.keyMap.Tab,
959 s.keyMap.Tab,
839 s.keyMap.LeftRight,
960 s.keyMap.LeftRight,
963+ return []key.Binding{}
842− return []key.Binding{}
845func (s *splashCmp) getMaxInfoWidth() int {
967func (s *splashCmp) getMaxInfoWidth() int {
@@ -940,3 +1062,11 @@ func (s *splashCmp) IsClaudeOAuthURLState() bool {
940func (s *splashCmp) IsClaudeOAuthComplete() bool {
1062func (s *splashCmp) IsClaudeOAuthComplete() bool {
941 return s.showClaudeOAuth2 && s.claudeOAuth2.State == claude.OAuthStateCode && s.claudeOAuth2.ValidationState == claude.OAuthValidationStateValid
1063 return s.showClaudeOAuth2 && s.claudeOAuth2.State == claude.OAuthStateCode && s.claudeOAuth2.ValidationState == claude.OAuthValidationStateValid
1066+func (s *splashCmp) IsShowingHyperOAuth2() bool {
1067+ return s.showHyperDeviceFlow
1070+func (s *splashCmp) IsShowingCopilotOAuth2() bool {
1071+ return s.showCopilotDeviceFlow