func generateLink(lh *linkHandler) tea.Cmd {
	return func() tea.Msg {
		select {
		case err := <-lh.err:
			return errMsg{err}
		case tok := <-lh.token:
			return linkTokenCreatedMsg(tok)
		}
	}
}
generateLink waits for either a link to be generated, or an error.
package linkgen
import (
	"fmt"
	"github.com/charmbracelet/bubbles/spinner"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/charm"
	"github.com/charmbracelet/charm/ui/charmclient"
	"github.com/charmbracelet/charm/ui/common"
	"github.com/charmbracelet/charm/ui/keygen"
	"github.com/muesli/reflow/indent"
)
type status int
const (
	initCharmClient status = iota  
	keygenRunning
	keygenFinished
	linkInit  
	linkTokenCreated
	linkRequested
	linkSuccess
	linkRequestDenied
	linkTimedOut
	linkError
	quitting
)
type linkTokenCreatedMsg string
type linkRequestMsg linkRequest
type linkSuccessMsg bool  
type linkTimeoutMsg struct{}
type errMsg struct {
	err error
}
func NewProgram(cfg *charm.Config) *tea.Program {
	m := NewModel(cfg)
	m.standalone = true
	return tea.NewProgram(m)
}
type Model struct {
	lh            *linkHandler
	standalone    bool           
	cfg           *charm.Config  
	Quit          bool           
	Exit          bool           
	err           error
	status        status
	alreadyLinked bool
	token         string
	linkRequest   linkRequest
	cc            *charm.Client
	buttonIndex   int  
	spinner       spinner.Model
	keygen        keygen.Model
}
func (m Model) acceptRequest() (Model, tea.Cmd) {
	m.lh.response <- true
	return m, nil
}
func (m Model) rejectRequest() (Model, tea.Cmd) {
	m.lh.response <- false
	m.status = linkRequestDenied
	if m.standalone {
		return m, tea.Quit
	}
	return m, nil
}
func NewModel(cfg *charm.Config) Model {
	lh := &linkHandler{
		err:      make(chan error),
		token:    make(chan string),
		request:  make(chan linkRequest),
		response: make(chan bool),
		success:  make(chan bool),
		timeout:  make(chan struct{}),
	}
	s := spinner.NewModel()
	s.Spinner = common.Spinner
	s.ForegroundColor = "241"
	return Model{
		lh:            lh,
		standalone:    false,
		cfg:           cfg,
		Quit:          false,
		Exit:          false,
		err:           nil,
		status:        linkInit,
		alreadyLinked: false,
		token:         "",
		linkRequest:   linkRequest{},
		buttonIndex:   0,
		spinner:       s,
	}
}
func (m *Model) SetCharmClient(cc *charm.Client) {
	if cc == nil {
		panic("charm client is nil")
	}
	m.cc = cc
}
func (m Model) Init() tea.Cmd {
	return tea.Batch(charmclient.NewClient(m.cfg), spinner.Tick)
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "ctrl+c":
			if m.standalone {
				m.status = quitting
				return m, tea.Quit
			}
			m.Quit = true
			return m, nil
		case "q", "esc":
			if m.standalone {
				m.status = quitting
				return m, tea.Quit
			}
			m.Exit = true
			return m, nil
		default:
			switch m.status {
			case linkRequested:
				switch msg.String() {
				case "j", "h", "right", "tab":
					m.buttonIndex++
					if m.buttonIndex > 1 {
						m.buttonIndex = 0
					}
				case "k", "l", "left", "shift+tab":
					m.buttonIndex--
					if m.buttonIndex < 0 {
						m.buttonIndex = 1
					}
				case "enter":
					if m.buttonIndex == 0 {
						return m.acceptRequest()
					}
					return m.rejectRequest()
				case "y":
					return m.acceptRequest()
				case "n":
					return m.rejectRequest()
				}
				return m, nil
			case linkSuccess, linkRequestDenied, linkTimedOut:
				m.Exit = true
				return m, nil
			}
		}
	case charmclient.NewClientMsg:
		m.cc = msg
		m.status = linkInit
		return m, InitLinkGen(m)
	case charmclient.ErrMsg:
		m.err = msg.Err
		return m, tea.Quit
	case charmclient.SSHAuthErrorMsg:
		if m.status == initCharmClient {
			m.status = keygenRunning
			m.keygen = keygen.NewModel()
			return m, keygen.GenerateKeys
		}
		m.err = msg.Err
		return m, tea.Quit
	case keygen.DoneMsg:
		m.status = keygenFinished
		return m, charmclient.NewClient(m.cfg)
	case errMsg:
		m.status = linkError
		m.err = msg.err
		return m, nil
	case linkTokenCreatedMsg:
		m.status = linkTokenCreated
		m.token = string(msg)
		return m, nil
	case linkRequestMsg:
		m.status = linkRequested
		m.linkRequest = linkRequest(msg)
		return m, nil
	case linkSuccessMsg:
		m.status = linkSuccess
		m.alreadyLinked = bool(msg)
		if m.standalone {
			return m, tea.Quit
		}
		return m, nil
	case linkTimeoutMsg:
		m.status = linkTimedOut
		if m.standalone {
			return m, tea.Quit
		}
		return m, nil
	case spinner.TickMsg:
		switch m.status {
		case initCharmClient, keygenRunning, linkInit:
			newSpinnerModel, cmd := m.spinner.Update(msg)
			m.spinner = newSpinnerModel
			return m, cmd
		}
		return m, nil
	}
	if m.status == keygenRunning {
		newModel, cmd := m.keygen.Update(msg)
		keygenModel, ok := newModel.(keygen.Model)
		if !ok {
			panic("could not perform assertion on keygen model")
		}
		m.keygen = keygenModel
		return m, cmd
	}
	return m, nil
}
func (m Model) View() string {
	var s string
	preamble := common.Wrap(fmt.Sprintf(
		"You can %s the SSH keys on another machine to your Charm account so both machines have access to your stuff. You can unlink keys at any time.\n\n",
		common.Keyword("link"),
	))
	switch m.status {
	case initCharmClient:
		s += preamble
		s += m.spinner.View() + " Initializing..."
	case keygenRunning:
		s += preamble
		if m.keygen.Status != keygen.StatusSuccess {
			s += m.spinner.View()
		}
		s += m.keygen.View()
	case linkInit:
		s += preamble
		s += m.spinner.View() + " Generating link..."
	case linkTokenCreated:
		s += preamble
		s += fmt.Sprintf(
			"%s\n\n%s\n\n%s",
			common.Wrap("To link, run the following command on your other machine:"),
			common.Code("charm link "+m.token),
			common.HelpView("To cancel, press escape"),
		)
	case linkRequested:
		var d []string
		s += preamble
		s += "Link request from:\n\n"
		d = append(d, []string{"IP", m.linkRequest.requestAddr}...)
		if len(m.linkRequest.pubKey) > 50 {
			d = append(d, []string{"Key", m.linkRequest.pubKey[0:50] + "..."}...)
		}
		s += common.KeyValueView(d...)
		s += "\n\nLink this device?\n\n"
		s += fmt.Sprintf(
			"%s %s",
			common.YesButtonView(m.buttonIndex == 0),
			common.NoButtonView(m.buttonIndex == 1),
		)
	case linkError:
		s += preamble
		s += "Uh oh: " + m.err.Error()
	case linkSuccess:
		s += common.Keyword("Linked!")
		if m.alreadyLinked {
			s += " This key is already linked, btw."
		}
		if m.standalone {
			s += "\n"
		} else {
			s = preamble + s + common.HelpView("\n\nPress any key to exit...")
		}
	case linkRequestDenied:
		s += "Link request " + common.Keyword("denied") + "."
		if m.standalone {
			s += "\n"
		} else {
			s = preamble + s + common.HelpView("\n\nPress any key to exit...")
		}
	case linkTimedOut:
		s += preamble
		s += "Link request timed out."
		if m.standalone {
			s += "\n"
		} else {
			s += common.HelpView("Press any key to exit...")
		}
	case quitting:
		s += "Linking canceled.\n"
	}
	if m.standalone {
		s = fmt.Sprintf("\n%s\n", indent.String(s, 2))
	}
	return s
}
func InitLinkGen(m Model) tea.Cmd {
	return tea.Batch(append(HandleLinkRequest(m), spinner.Tick)...)
}
func HandleLinkRequest(m Model) []tea.Cmd {
	go func() {
		if err := m.cc.LinkGen(m.lh); err != nil {
			m.lh.err <- err
		}
	}()
	return []tea.Cmd{
		generateLink(m.lh),
		handleLinkRequest(m.lh),
		handleLinkSuccess(m.lh),
		handleLinkTimeout(m.lh),
		handleLinkError(m.lh),
	}
}