tui, cli: a plugin's account whose model list failed says why on the Providers page and in magpie providers (gnayiab on X) — v0.1.1001 had the app's editor say why Cursor's plugin account listed Auto alone, but gnayiab runs magpie's TUI in WSL, where the Providers page still showed "1 models" and nothing else (only m, asking again, said why). Each provider's row now ends in "couldn't list its models: <why>" (red, as other faults there) when provider.ListError has one, and magpie providers says the same in amber after the row. TestProvidersSayWhyAListFellBack fails without the change. go vet, go test -tags nogui ./internal/tui . under a temp HOME, and go build (GOOS=linux nogui, GOOS=windows) pass.

This commit is contained in:
yetone committed 2026-10-05 18:44:12 +08:00
1 parent 1534382ff9
commit f3c05b5ed8
3 files changed
+60 -2

No files matched your search

+39
View File
@@ -0,0 +1,39 @@
package tui
import (
"strings"
"testing"
"github.com/yetone/magpie/internal/provider"
)
// A plugin's account showing its defaults alone says why on the
// Providers page, as the app's editor does (gnayiab on X: magpie's TUI in
// WSL had Cursor with Auto alone and nothing said).
func TestProvidersSayWhyAListFellBack(t *testing.T) {
home(t)
m := model{w: 160, h: 40}
m.provs = []provider.Provider{{ID: "cursor", Name: "Cursor"}, {ID: "deepseek", Name: "DeepSeek"}}
listError = func(p provider.Provider) string {
if p.ID == "cursor" {
return "fetch failed: unable to get local issuer certificate"
}
return ""
}
t.Cleanup(func() { listError = provider.Provider.ListError })
var cursor, deepseek string
for _, l := range strings.Split(m.viewProviders(), "\n") {
switch {
case strings.Contains(l, "Cursor"):
cursor = l
case strings.Contains(l, "DeepSeek"):
deepseek = l
}
}
if !strings.Contains(cursor, "couldn't list its models: fetch failed: unable to get local issuer certificate") {
t.Fatalf("Cursor's row doesn't say why: %q", cursor)
}
if strings.Contains(deepseek, "couldn't list") {
t.Fatalf("DeepSeek's row says a list failed: %q", deepseek)
}
}
+18 -2
View File
@@ -498,6 +498,9 @@ func endpointAsk(pr provider.PresetDef, crumbs []string, now string, then func(s
}}
}
// listError is provider.Provider.ListError; tests stand in for it.
var listError = provider.Provider.ListError
func (m model) viewProviders() string {
var b strings.Builder
b.WriteString(m.header())
@@ -511,7 +514,7 @@ func (m model) viewProviders() string {
}
return b.String()
}
type row struct{ name, id, key, models, note string }
type row struct{ name, id, key, models, note, warn string }
var rows []row
var w [4]int
for _, p := range m.provs {
@@ -540,6 +543,12 @@ func (m model) viewProviders() string {
notes = append(notes, "groups only")
}
r.note = strings.Join(notes, " · ")
// a plugin's account showing its defaults alone says why, as the
// app's editor does (gnayiab on X: Cursor in WSL's TUI had Auto
// alone and nothing said)
if e := listError(p); e != "" {
r.warn = "couldn't list its models: " + e
}
for i, s := range []string{r.name, r.id, r.key, r.models} {
w[i] = max(w[i], lipgloss.Width(s))
}
@@ -560,7 +569,14 @@ func (m model) viewProviders() string {
if strings.HasPrefix(r.key, "○") {
key = sBad.Render(padRight(r.key, w[2]))
}
b.WriteString(pad + marker + name + " " + sFaint.Render(padRight(r.id, w[1])) + " " + key + " " + sText.Render(padRight(r.models, w[3])) + " " + sMuted.Render(r.note) + "\n")
line := pad + marker + name + " " + sFaint.Render(padRight(r.id, w[1])) + " " + key + " " + sText.Render(padRight(r.models, w[3])) + " " + sMuted.Render(r.note)
if r.warn != "" {
if r.note != "" {
line += sMuted.Render(" · ")
}
line += sBad.Render(r.warn)
}
b.WriteString(line + "\n")
}
return strings.TrimRight(b.String(), "\n")
}
+3
View File
@@ -116,6 +116,9 @@ func providers() error {
if u := uses[p.ID]; len(u) > 0 {
r.uses = green.Render("← " + strings.Join(u, ", "))
}
if e := p.ListError(); e != "" {
r.uses += amber.Render(" ! couldn't list its models: " + e)
}
if len(p.Fallback) > 0 {
r.uses += muted.Render(" ⤷ " + strings.Join(p.Fallback, " → "))
}