package main import ( "fmt" "os" "os/exec" "path/filepath" "strings" tea "github.com/charmbracelet/bubbletea" ) type DashboardModel struct { checks []CheckResult cursor int maxCursor int output string width int } func NewDashboardModel(checks []CheckResult) DashboardModel { return DashboardModel{ checks: checks, maxCursor: 2, } } type checksDoneMsg struct { results []CheckResult } type launchResultMsg struct { err error } type validateResultMsg struct { output string err error } func runChecksCmd() tea.Cmd { return func() tea.Msg { return checksDoneMsg{results: RunChecks()} } } func launchOpencodeCmd() tea.Cmd { home, _ := os.UserHomeDir() wrapper := filepath.Join(home, ".local", "bin", "opencode") cmd := exec.Command(wrapper) return tea.ExecProcess(cmd, func(err error) tea.Msg { return launchResultMsg{err: err} }) } func validateProfileCmd() tea.Cmd { return func() tea.Msg { cmd := exec.Command("nono", "profile", "validate", "opencode-tinfoil") out, err := cmd.CombinedOutput() return validateResultMsg{output: strings.TrimSpace(string(out)), err: err} } } func (m DashboardModel) Update(msg tea.Msg) (DashboardModel, tea.Cmd) { switch msg := msg.(type) { case checksDoneMsg: m.checks = msg.results return m, nil case launchResultMsg: if msg.err != nil { m.output = fmt.Sprintf("Launch failed: %v", msg.err) } else { m.output = "opencode exited normally" } return m, nil case validateResultMsg: if msg.err != nil { m.output = "Validation: " + msg.output } else { m.output = "Validation OK: " + msg.output } return m, nil case tea.KeyMsg: switch msg.String() { case "up", "k": if m.cursor > 0 { m.cursor-- } case "down", "j": if m.cursor < m.maxCursor { m.cursor++ } case "l": if m.cursor == 0 { return m, launchOpencodeCmd() } case "v": if m.cursor == 1 { return m, validateProfileCmd() } case "r": m.output = "Refreshing..." return m, runChecksCmd() } } return m, nil } func (m DashboardModel) View() string { var b strings.Builder b.WriteString("Dashboard\n\n") for _, check := range m.checks { var icon string style := checkOKStyle if check.Status == StatusOK { icon = "✓" } else { icon = "✗" style = checkFailStyle } b.WriteString(fmt.Sprintf(" %s %s %s\n", style.Render(icon), labelStyle.Render(check.Label), detailStyle.Render(check.Detail), )) } b.WriteString("\n") actions := []string{"Launch opencode in sandbox", "Validate profile", "Refresh checks"} for i, action := range actions { cursor := " " if i == m.cursor { cursor = cursorStyle.Render("> ") } b.WriteString(fmt.Sprintf("%s%s\n", cursor, action)) } if m.output != "" { b.WriteString("\n" + detailStyle.Render(m.output)) } b.WriteString("\n\n" + helpStyle.Render(" l:launch v:validate r:refresh Tab:editor")) return b.String() }