fix: lint errors

This commit is contained in:
dataCenter430 2026-03-31 17:31:29 +02:00
parent 7cdd86df8b
commit 2f3e2ad9cf
8 changed files with 222 additions and 62 deletions

View file

@ -0,0 +1,177 @@
package cliui
import (
"testing"
flag "github.com/spf13/pflag"
)
func init() {
// Disable ANSI colors in tests so output is predictable plain text.
Init(true)
}
// ---------------------------------------------------------------------------
// showErrHint
// ---------------------------------------------------------------------------
func TestShowErrHint(t *testing.T) {
cases := []struct {
msg string
want bool
}{
// Cobra flag errors — should show hint
{"unknown flag: --foo", true},
{"unknown shorthand flag: 'f' in -f", true},
{"flag needs an argument: --output", true},
{"required flag(s) \"model\" not set", true},
// Generic invalid-argument errors — should show hint
{"invalid argument \"abc\" for --count", true},
// usage: in message — should show hint
{"bad input\nusage: picoclaw ...", true},
// Should NOT false-positive on unrelated "flag" words
{"connection flagged by remote", false},
{"feature flag not set", false},
{"please flag this issue", false},
// Unrelated messages — no hint
{"something went wrong", false},
{"network timeout", false},
}
for _, tc := range cases {
got := showErrHint(tc.msg)
if got != tc.want {
t.Errorf("showErrHint(%q) = %v, want %v", tc.msg, got, tc.want)
}
}
}
// ---------------------------------------------------------------------------
// styleUsageTokens
// ---------------------------------------------------------------------------
func TestStyleUsageTokensContainsTokens(t *testing.T) {
cases := []struct {
input string
contains []string // substrings that must appear in plain output
}{
{
"picoclaw agent <message>",
[]string{"picoclaw agent", "<message>"},
},
{
"picoclaw [command] [flags]",
[]string{"picoclaw", "[command]", "[flags]"},
},
{
"picoclaw",
[]string{"picoclaw"},
},
{
"cmd <arg1> [--flag]",
[]string{"cmd", "<arg1>", "[--flag]"},
},
}
for _, tc := range cases {
out := styleUsageTokens(tc.input)
for _, sub := range tc.contains {
if !containsStripped(out, sub) {
t.Errorf("styleUsageTokens(%q): output %q does not contain %q", tc.input, out, sub)
}
}
}
}
// containsStripped checks whether plain contains sub after stripping ANSI escapes.
// Since Init(true) sets Ascii profile, lipgloss emits no escape codes in tests,
// so this is just a plain substring check.
func containsStripped(plain, sub string) bool {
return len(plain) >= len(sub) && findSubstring(plain, sub)
}
func findSubstring(s, sub string) bool {
for i := 0; i <= len(s)-len(sub); i++ {
if s[i:i+len(sub)] == sub {
return true
}
}
return false
}
// ---------------------------------------------------------------------------
// collectFlagRows
// ---------------------------------------------------------------------------
func TestCollectFlagRows_Empty(t *testing.T) {
fs := flag.NewFlagSet("test", flag.ContinueOnError)
rows := collectFlagRows(fs)
if len(rows) != 0 {
t.Fatalf("expected 0 rows for empty FlagSet, got %d", len(rows))
}
}
func TestCollectFlagRows_BasicFlags(t *testing.T) {
fs := flag.NewFlagSet("test", flag.ContinueOnError)
fs.String("output", "", "output file path")
fs.Bool("verbose", false, "enable verbose mode")
fs.Int("count", 1, "number of items")
rows := collectFlagRows(fs)
if len(rows) != 3 {
t.Fatalf("expected 3 rows, got %d", len(rows))
}
// Rows must be sorted alphabetically by flag name.
names := []string{}
for _, r := range rows {
names = append(names, r[0])
}
if names[0] > names[1] || names[1] > names[2] {
t.Errorf("rows not sorted: %v", names)
}
}
func TestCollectFlagRows_Shorthand(t *testing.T) {
fs := flag.NewFlagSet("test", flag.ContinueOnError)
fs.StringP("model", "m", "", "model name")
rows := collectFlagRows(fs)
if len(rows) != 1 {
t.Fatalf("expected 1 row, got %d", len(rows))
}
left := rows[0][0]
if !findSubstring(left, "-m") || !findSubstring(left, "--model") {
t.Errorf("expected shorthand and long form in %q", left)
}
}
func TestCollectFlagRows_HiddenFlagsExcluded(t *testing.T) {
fs := flag.NewFlagSet("test", flag.ContinueOnError)
fs.String("visible", "", "this shows up")
hidden := fs.String("hidden", "", "this should not show up")
_ = hidden
_ = fs.MarkHidden("hidden")
rows := collectFlagRows(fs)
if len(rows) != 1 {
t.Fatalf("expected 1 row (hidden excluded), got %d", len(rows))
}
if !findSubstring(rows[0][0], "visible") {
t.Errorf("expected visible flag in rows, got %q", rows[0][0])
}
}
func TestCollectFlagRows_UsageInRightColumn(t *testing.T) {
fs := flag.NewFlagSet("test", flag.ContinueOnError)
fs.String("format", "json", "output format: json or text")
rows := collectFlagRows(fs)
if len(rows) != 1 {
t.Fatalf("expected 1 row, got %d", len(rows))
}
if rows[0][1] != "output format: json or text" {
t.Errorf("expected usage in right column, got %q", rows[0][1])
}
}

View file

@ -67,7 +67,8 @@ func FormatCLIError(msg string, ctx *cobra.Command) string {
func showErrHint(msg string) bool {
m := strings.ToLower(msg)
return strings.Contains(m, "unknown flag") ||
strings.Contains(m, "flag") ||
strings.Contains(m, "unknown shorthand flag") ||
strings.Contains(m, "flag needs an argument") ||
strings.Contains(m, "invalid") ||
strings.Contains(m, "required") ||
strings.Contains(m, "usage:")

View file

@ -7,6 +7,12 @@ import (
"github.com/charmbracelet/lipgloss"
)
// ProviderRow holds one provider's display name and status value.
type ProviderRow struct {
Name string
Val string
}
// StatusReport is a structured status view for PrintStatus.
type StatusReport struct {
Logo string
@ -17,9 +23,7 @@ type StatusReport struct {
WorkspacePath string
WorkspaceOK bool
Model string
// ProviderNames and ProviderVals same length
ProviderNames []string
ProviderVals []string
Providers []ProviderRow
OAuthLines []string // each full line "provider (method): state"
}
@ -45,8 +49,8 @@ func printStatusPlain(r StatusReport) {
if r.ConfigOK {
fmt.Printf("Model: %s\n", r.Model)
for i := range r.ProviderNames {
fmt.Printf("%s: %s\n", r.ProviderNames[i], r.ProviderVals[i])
for _, p := range r.Providers {
fmt.Printf("%s: %s\n", p.Name, p.Val)
}
if len(r.OAuthLines) > 0 {
fmt.Println("\nOAuth/Token Auth:")
@ -80,7 +84,7 @@ func printStatusFancy(r StatusReport) {
fmt.Println(topBox.Render(head.String()))
fmt.Println()
if UseColumnLayout() && len(r.ProviderNames) > 0 && r.ConfigOK {
if UseColumnLayout() && len(r.Providers) > 0 && r.ConfigOK {
leftW := (inner - 2) / 2
rightW := inner - leftW - 2
pathsNarrow := pathStatusPanel(r, leftW)
@ -89,7 +93,7 @@ func printStatusFancy(r StatusReport) {
fmt.Println(lipgloss.JoinHorizontal(lipgloss.Top, pathsNarrow, gap, prov))
} else {
fmt.Println(pathStatusPanel(r, inner))
if len(r.ProviderNames) > 0 && r.ConfigOK {
if len(r.Providers) > 0 && r.ConfigOK {
fmt.Println(providerTablePanel(r, inner))
}
}
@ -130,7 +134,7 @@ func statusMark(ok bool) string {
}
func providerTablePanel(r StatusReport, colW int) string {
if len(r.ProviderNames) == 0 {
if len(r.Providers) == 0 {
return ""
}
keyW := min(22, colW/3)
@ -144,9 +148,9 @@ func providerTablePanel(r StatusReport, colW int) string {
var b strings.Builder
b.WriteString(titleBarStyle().Render("Providers & local") + "\n\n")
for i := range r.ProviderNames {
k := lipgloss.NewStyle().Foreground(accentBlue).Bold(true).Width(keyW).Render(r.ProviderNames[i])
v := styleProviderVal(r.ProviderVals[i]).Width(valW).Render(r.ProviderVals[i])
for _, p := range r.Providers {
k := lipgloss.NewStyle().Foreground(accentBlue).Bold(true).Width(keyW).Render(p.Name)
v := styleProviderVal(p.Val).Width(valW).Render(p.Val)
b.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, k, " ", v))
b.WriteString("\n")
}

View file

@ -6,7 +6,7 @@ import (
"github.com/spf13/cobra"
)
//go:generate bash -lc "rm -rf workspace && cp -r ../../../../workspace ./workspace"
//go:generate cp -r ../../../../workspace .
//go:embed workspace
var embeddedFiles embed.FS

View file

@ -98,53 +98,30 @@ func statusCmd() {
}
ollamaBase, hasOllama := findProtocolBase("ollama")
status := func(enabled bool) string {
val := func(enabled bool, extra ...string) string {
if enabled {
if len(extra) > 0 && extra[0] != "" {
return "✓ " + extra[0]
}
return "✓"
}
return "not set"
}
report.ProviderNames = []string{
"OpenRouter API",
"Anthropic API",
"OpenAI API",
"Gemini API",
"Zhipu API",
"Qwen API",
"Groq API",
"Moonshot API",
"DeepSeek API",
"VolcEngine API",
"Nvidia API",
}
report.ProviderVals = []string{
status(hasOpenRouter),
status(hasAnthropic),
status(hasOpenAI),
status(hasGemini),
status(hasZhipu),
status(hasQwen),
status(hasGroq),
status(hasMoonshot),
status(hasDeepSeek),
status(hasVolcEngine),
status(hasNvidia),
}
if hasVLLM {
report.ProviderNames = append(report.ProviderNames, "vLLM / local")
report.ProviderVals = append(report.ProviderVals, "✓ "+vllmBase)
} else {
report.ProviderNames = append(report.ProviderNames, "vLLM / local")
report.ProviderVals = append(report.ProviderVals, "not set")
}
if hasOllama {
report.ProviderNames = append(report.ProviderNames, "Ollama")
report.ProviderVals = append(report.ProviderVals, "✓ "+ollamaBase)
} else {
report.ProviderNames = append(report.ProviderNames, "Ollama")
report.ProviderVals = append(report.ProviderVals, "not set")
report.Providers = []cliui.ProviderRow{
{"OpenRouter API", val(hasOpenRouter)},
{"Anthropic API", val(hasAnthropic)},
{"OpenAI API", val(hasOpenAI)},
{"Gemini API", val(hasGemini)},
{"Zhipu API", val(hasZhipu)},
{"Qwen API", val(hasQwen)},
{"Groq API", val(hasGroq)},
{"Moonshot API", val(hasMoonshot)},
{"DeepSeek API", val(hasDeepSeek)},
{"VolcEngine API", val(hasVolcEngine)},
{"Nvidia API", val(hasNvidia)},
{"vLLM / local", val(hasVLLM, vllmBase)},
{"Ollama", val(hasOllama, ollamaBase)},
}
store, _ := auth.LoadStore()

View file

@ -13,9 +13,9 @@ import (
"github.com/spf13/cobra"
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
"github.com/sipeed/picoclaw/cmd/picoclaw/internal/cliui"
"github.com/sipeed/picoclaw/cmd/picoclaw/internal/agent"
"github.com/sipeed/picoclaw/cmd/picoclaw/internal/auth"
"github.com/sipeed/picoclaw/cmd/picoclaw/internal/cliui"
"github.com/sipeed/picoclaw/cmd/picoclaw/internal/cron"
"github.com/sipeed/picoclaw/cmd/picoclaw/internal/gateway"
"github.com/sipeed/picoclaw/cmd/picoclaw/internal/migrate"
@ -40,7 +40,8 @@ func earlyColorDisabled() bool {
return true
}
for i := 1; i < len(os.Args); i++ {
if os.Args[i] == "--no-color" {
arg := os.Args[i]
if arg == "--no-color" || arg == "--no-color=true" || arg == "--no-color=1" {
return true
}
}