From d3fdfe30b978ed94a4cfd8dcc072788d59cd34eb Mon Sep 17 00:00:00 2001 From: vikingowl Date: Thu, 7 May 2026 17:15:46 +0200 Subject: [PATCH] feat(cli): add 'gnoma providers' subcommand Lists configured provider, auto-discovered CLI agents (claude/gemini/vibe), running local models (ollama/llamacpp), and SLM status in one shot. --- cmd/gnoma/main.go | 86 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/cmd/gnoma/main.go b/cmd/gnoma/main.go index a4fcbf8..193430b 100644 --- a/cmd/gnoma/main.go +++ b/cmd/gnoma/main.go @@ -76,6 +76,7 @@ func main() { fmt.Fprintf(os.Stderr, " gnoma [flags] open interactive TUI\n") fmt.Fprintf(os.Stderr, " gnoma [flags] run a single prompt (pipe mode)\n") fmt.Fprintf(os.Stderr, "\nSubcommands:\n") + fmt.Fprintf(os.Stderr, " gnoma providers list all discovered providers and models\n") fmt.Fprintf(os.Stderr, " gnoma slm setup download and verify the llamafile model\n") fmt.Fprintf(os.Stderr, " gnoma slm status show SLM setup state\n") fmt.Fprintf(os.Stderr, "\nFlags:\n") @@ -142,9 +143,14 @@ func main() { *permMode = cfg.Permission.Mode } - // SLM subcommands: `gnoma slm setup` / `gnoma slm status` - if cliArgs := flag.Args(); len(cliArgs) > 0 && cliArgs[0] == "slm" { - os.Exit(runSLMCommand(cliArgs[1:], cfg, logger)) + // Subcommand dispatch (before provider creation) + if cliArgs := flag.Args(); len(cliArgs) > 0 { + switch cliArgs[0] { + case "providers": + os.Exit(runProvidersCommand(cfg, logger)) + case "slm": + os.Exit(runSLMCommand(cliArgs[1:], cfg, logger)) + } } knownProviders := map[string]bool{ @@ -1110,6 +1116,80 @@ func (s *stubProvider) Stream(_ context.Context, _ provider.Request) (stream.Str return nil, fmt.Errorf("%s", s.reason) } +// runProvidersCommand handles `gnoma providers`. +// Prints all auto-discovered providers, CLI agents, local models, and SLM status. +func runProvidersCommand(cfg *gnomacfg.Config, logger *slog.Logger) int { + ctx := context.Background() + + fmt.Println("Configured provider:") + if cfg.Provider.Default != "" { + key := cfg.Provider.APIKeys[cfg.Provider.Default] + keyHint := "(no key)" + if key != "" { + if len(key) > 8 { + keyHint = key[:4] + "..." + key[len(key)-4:] + } else { + keyHint = "(set)" + } + } + model := cfg.Provider.Model + if model == "" { + model = "(default)" + } + fmt.Printf(" %-12s model: %-30s key: %s\n", cfg.Provider.Default, model, keyHint) + } else { + fmt.Println(" (none — set [provider] in ~/.config/gnoma/config.toml)") + } + + fmt.Println("\nCLI agents (auto-discovered):") + agents := subprocprov.DiscoverCLIAgents(ctx) + if len(agents) == 0 { + fmt.Println(" (none found on PATH)") + } + for _, a := range agents { + fmt.Printf(" %-12s version: %-20s path: %s\n", a.Name, a.Version, a.Path) + } + + fmt.Println("\nLocal models (ollama / llama.cpp):") + localModels := router.DiscoverLocalModels(ctx, logger, + cfg.Provider.Endpoints["ollama"], + cfg.Provider.Endpoints["llamacpp"], + nil, + ) + if len(localModels) == 0 { + fmt.Println(" (none running)") + } + for _, m := range localModels { + tools := "no tools" + if m.SupportsTools { + tools = "tools" + } + fmt.Printf(" %-12s model: %-30s %s\n", m.Provider, m.ID, tools) + } + + fmt.Println("\nSLM (llamafile):") + slmDataDir := cfg.SLM.DataDir + if slmDataDir == "" { + slmDataDir = slm.DefaultDataDir() + } + slmMgr := slm.New(slm.Config{DataDir: slmDataDir, ModelURL: cfg.SLM.ModelURL}, logger) + status := slmMgr.Status() + enabled := "disabled" + if cfg.SLM.Enabled { + enabled = "enabled" + } + fmt.Printf(" status: %-10s (%s)\n", status, enabled) + if mf := slmMgr.Manifest(); mf != nil { + fmt.Printf(" file: %s (%s)\n", mf.FilePath, humanBytes(mf.Size)) + } + + fmt.Println("\nTo set a provider:") + fmt.Println(" gnoma --provider anthropic --model claude-opus-4-5 (one-off)") + fmt.Println(" gnoma config set provider.default anthropic (permanent)") + + return 0 +} + // runSLMCommand handles `gnoma slm `. // Returns an exit code. func runSLMCommand(args []string, cfg *gnomacfg.Config, logger *slog.Logger) int {