func (m Model) View() string {
	if m.err != nil {
		return m.err.Error()
	}
	var s string
	switch m.state {
	case stateInitCharmClient:
		s = m.spinner.View() + " Initializing...\n\n"
	case stateKeygenRunning:
		if m.keygen.Status != keygen.StatusSuccess {
			s += m.spinner.View()
		}
		s += m.keygen.View()
	case stateLoading:
		s = m.spinner.View() + " Loading...\n\n"
	case stateQuitting:
		s = "Thanks for using Charm!\n"
	default:
		s = "Here are the keys linked to your Charm account.\n\n"
		s += keysView(m)
		if m.pager.TotalPages > 1 {
			s += m.pager.View()
		}
		switch m.state {
		case stateDeletingKey:
			s += promptDeleteView()
		case stateDeletingActiveKey:
			s += promptDeleteActiveKeyView()
		case stateDeletingAccount:
			s += promptDeleteAccountView()
		default:
			s += "\n\n" + helpView(m)
		}
	}
	if m.standalone {
		return indent.String(fmt.Sprintf("\n%s\n", s), 2)
	}
	return s
}
View renders the current UI into a string.
package keys
import (
	"fmt"
	pager "github.com/charmbracelet/bubbles/paginator"
	"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"
	te "github.com/muesli/termenv"
)
const keysPerPage = 4
type state int
const (
	stateInitCharmClient state = iota
	stateKeygenRunning
	stateKeygenFinished
	stateLoading
	stateNormal
	stateDeletingKey
	stateDeletingActiveKey
	stateDeletingAccount
	stateQuitting
)
type keyState int
const (
	keyNormal keyState = iota
	keySelected
	keyDeleting
)
func NewProgram(cfg *charm.Config) *tea.Program {
	m := NewModel(cfg)
	m.standalone = true
	return tea.NewProgram(m)
}
type keysLoadedMsg charm.Keys
type unlinkedKeyMsg int
type errMsg struct {
	err error
}
type Model struct {
	cc             *charm.Client
	cfg            *charm.Config
	pager          pager.Model
	state          state
	err            error
	standalone     bool
	activeKeyIndex int          
	keys           []charm.Key  
	index          int          
	Exit           bool
	Quit           bool
	spinner        spinner.Model
	keygen         keygen.Model
}
func (m *Model) getSelectedIndex() int {
	return m.index + m.pager.Page*m.pager.PerPage
}
func (m *Model) UpdatePaging(msg tea.Msg) {
	m.pager.SetTotalPages(len(m.keys))
	m.pager, _ = m.pager.Update(msg)
	numItems := m.pager.ItemsOnPage(len(m.keys))
	m.index = min(m.index, numItems-1)
}
func (m *Model) SetCharmClient(cc *charm.Client) {
	if cc == nil {
		panic("charm client is nil")
	}
	m.cc = cc
}
func NewModel(cfg *charm.Config) Model {
	p := pager.NewModel()
	p.PerPage = keysPerPage
	p.Type = pager.Dots
	p.InactiveDot = te.String("•").
		Foreground(common.NewColorPair("#4F4F4F", "#CACACA").Color()).
		String()
	s := spinner.NewModel()
	s.Spinner = common.Spinner
	s.ForegroundColor = "241"
	return Model{
		cfg:            cfg,
		pager:          p,
		state:          stateLoading,
		err:            nil,
		activeKeyIndex: -1,
		keys:           []charm.Key{},
		index:          0,
		spinner:        s,
		Exit:           false,
		Quit:           false,
	}
}
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", "q", "esc":
			if m.standalone {
				m.state = stateQuitting
				return m, tea.Quit
			}
			m.Exit = true
			return m, nil
		case "up", "k":
			m.index--
			if m.index < 0 && m.pager.Page > 0 {
				m.index = m.pager.PerPage - 1
				m.pager.PrevPage()
			}
			m.index = max(0, m.index)
		case "down", "j":
			itemsOnPage := m.pager.ItemsOnPage(len(m.keys))
			m.index++
			if m.index > itemsOnPage-1 && m.pager.Page < m.pager.TotalPages-1 {
				m.index = 0
				m.pager.NextPage()
			}
			m.index = min(itemsOnPage-1, m.index)
		case "x":
			m.state = stateDeletingKey
			m.UpdatePaging(msg)
			return m, nil
		case "y":
			switch m.state {
			case stateDeletingKey:
				if len(m.keys) == 1 {
					m.state = stateDeletingAccount
					return m, nil
				}
				if m.getSelectedIndex() == m.activeKeyIndex {
					m.state = stateDeletingActiveKey
					return m, nil
				}
				m.state = stateNormal
				return m, unlinkKey(m)
			case stateDeletingActiveKey:
				fallthrough
			case stateDeletingAccount:
				m.state = stateQuitting
				return m, unlinkKey(m)
			}
		}
	case charmclient.ErrMsg:
		m.err = msg.Err
		return m, tea.Quit
	case charmclient.SSHAuthErrorMsg:
		if m.state == stateInitCharmClient {
			m.state = stateKeygenRunning
			m.keygen = keygen.NewModel()
			return m, keygen.GenerateKeys
		}
		m.err = msg.Err
		return m, tea.Quit
	case charmclient.NewClientMsg:
		m.cc = msg
		m.state = stateLoading
		return m, LoadKeys(m)
	case keygen.DoneMsg:
		m.state = stateKeygenFinished
		return m, charmclient.NewClient(m.cfg)
	case errMsg:
		m.err = msg.err
		return m, nil
	case keysLoadedMsg:
		m.state = stateNormal
		m.index = 0
		m.activeKeyIndex = msg.ActiveKey
		m.keys = msg.Keys
	case unlinkedKeyMsg:
		if m.state == stateQuitting {
			return m, tea.Quit
		}
		i := m.getSelectedIndex()
		m.keys = append(m.keys[:i], m.keys[i+1:]...)
		m.pager.SetTotalPages(len(m.keys))
		m.pager.Page = min(m.pager.Page, m.pager.TotalPages-1)
		m.index = min(m.index, m.pager.ItemsOnPage(len(m.keys)-1))
		return m, nil
	case spinner.TickMsg:
		var cmd tea.Cmd
		if m.state < stateNormal {
			m.spinner, cmd = m.spinner.Update(msg)
		}
		return m, cmd
	}
	if m.state == stateKeygenRunning {
		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
	}
	m.UpdatePaging(msg)
	k, ok := msg.(tea.KeyMsg)
	if ok && k.String() != "x" {
		m.state = stateNormal
	}
	return m, nil
}