From feaa4aba605a2ef0f857c58d1cd1eda4bc5a0e9f Mon Sep 17 00:00:00 2001 From: reidliu41 Date: Tue, 17 Feb 2026 21:14:10 +0800 Subject: [PATCH] feat(cli): add zeroclaw providers command to list supported providers - Add `zeroclaw providers` CLI command that lists all 28 supported AI providers - Each entry shows: config ID, display name, local/cloud tag, active marker, and aliases - Also shows `custom:` and `anthropic-custom:` escape hatches at the bottom Previously users had no way to discover available providers without reading source code. The unknown-provider error message suggests `run zeroclaw onboard --interactive` but doesn't list options. This command gives immediate visibility. --- src/main.rs | 24 +++++++++++++++++++++ src/providers/mod.rs | 51 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/src/main.rs b/src/main.rs index 181c046..1919afd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -192,6 +192,9 @@ enum Commands { model_command: ModelCommands, }, + /// List supported AI providers + Providers, + /// Manage channels (telegram, discord, slack) Channel { #[command(subcommand)] @@ -551,6 +554,27 @@ async fn main() -> Result<()> { } }, + Commands::Providers => { + let providers = providers::list_providers(); + let current = config.default_provider.as_deref().unwrap_or("openrouter"); + println!("Supported providers ({} total):\n", providers.len()); + println!(" {:<19} {}", "ID (use in config)", "DESCRIPTION"); + println!(" {:<19} {}", "───────────────────", "───────────"); + for p in &providers { + let marker = if p.name == current { " (active)" } else { "" }; + let local_tag = if p.local { " [local]" } else { "" }; + let aliases = if p.aliases.is_empty() { + String::new() + } else { + format!(" (aliases: {})", p.aliases.join(", ")) + }; + println!(" {:<19} {}{}{}{}", p.name, p.display_name, local_tag, marker, aliases); + } + println!("\n custom: Any OpenAI-compatible endpoint"); + println!(" anthropic-custom: Any Anthropic-compatible endpoint"); + Ok(()) + } + Commands::Service { service_command } => service::handle_command(&service_command, &config), Commands::Doctor => doctor::run(&config), diff --git a/src/providers/mod.rs b/src/providers/mod.rs index 85fa3ad..89d4b82 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -560,6 +560,57 @@ pub fn create_routed_provider( ))) } +/// Information about a supported provider for display purposes. +pub struct ProviderInfo { + /// Canonical name used in config (e.g. `"openrouter"`) + pub name: &'static str, + /// Human-readable display name + pub display_name: &'static str, + /// Alternative names accepted in config + pub aliases: &'static [&'static str], + /// Whether the provider runs locally (no API key required) + pub local: bool, +} + +/// Return the list of all known providers for display in `zeroclaw providers list`. +/// +/// This is intentionally separate from the factory match in `create_provider` +/// (display concern vs. construction concern). +pub fn list_providers() -> Vec { + vec![ + // ── Primary providers ──────────────────────────────── + ProviderInfo { name: "openrouter", display_name: "OpenRouter", aliases: &[], local: false }, + ProviderInfo { name: "anthropic", display_name: "Anthropic", aliases: &[], local: false }, + ProviderInfo { name: "openai", display_name: "OpenAI", aliases: &[], local: false }, + ProviderInfo { name: "ollama", display_name: "Ollama", aliases: &[], local: true }, + ProviderInfo { name: "gemini", display_name: "Google Gemini", aliases: &["google", "google-gemini"], local: false }, + // ── OpenAI-compatible providers ────────────────────── + ProviderInfo { name: "venice", display_name: "Venice", aliases: &[], local: false }, + ProviderInfo { name: "vercel", display_name: "Vercel AI Gateway", aliases: &["vercel-ai"], local: false }, + ProviderInfo { name: "cloudflare", display_name: "Cloudflare AI", aliases: &["cloudflare-ai"], local: false }, + ProviderInfo { name: "moonshot", display_name: "Moonshot", aliases: &["kimi"], local: false }, + ProviderInfo { name: "synthetic", display_name: "Synthetic", aliases: &[], local: false }, + ProviderInfo { name: "opencode", display_name: "OpenCode Zen", aliases: &["opencode-zen"], local: false }, + ProviderInfo { name: "zai", display_name: "Z.AI", aliases: &["z.ai"], local: false }, + ProviderInfo { name: "glm", display_name: "GLM (Zhipu)", aliases: &["zhipu"], local: false }, + ProviderInfo { name: "minimax", display_name: "MiniMax", aliases: &[], local: false }, + ProviderInfo { name: "bedrock", display_name: "Amazon Bedrock", aliases: &["aws-bedrock"], local: false }, + ProviderInfo { name: "qianfan", display_name: "Qianfan (Baidu)", aliases: &["baidu"], local: false }, + ProviderInfo { name: "qwen", display_name: "Qwen (DashScope)", aliases: &["dashscope", "qwen-intl", "dashscope-intl", "qwen-us", "dashscope-us"], local: false }, + ProviderInfo { name: "groq", display_name: "Groq", aliases: &[], local: false }, + ProviderInfo { name: "mistral", display_name: "Mistral", aliases: &[], local: false }, + ProviderInfo { name: "xai", display_name: "xAI (Grok)", aliases: &["grok"], local: false }, + ProviderInfo { name: "deepseek", display_name: "DeepSeek", aliases: &[], local: false }, + ProviderInfo { name: "together", display_name: "Together AI", aliases: &["together-ai"], local: false }, + ProviderInfo { name: "fireworks", display_name: "Fireworks AI", aliases: &["fireworks-ai"], local: false }, + ProviderInfo { name: "perplexity", display_name: "Perplexity", aliases: &[], local: false }, + ProviderInfo { name: "cohere", display_name: "Cohere", aliases: &[], local: false }, + ProviderInfo { name: "copilot", display_name: "GitHub Copilot", aliases: &["github-copilot"], local: false }, + ProviderInfo { name: "lmstudio", display_name: "LM Studio", aliases: &["lm-studio"], local: true }, + ProviderInfo { name: "nvidia", display_name: "NVIDIA NIM", aliases: &["nvidia-nim", "build.nvidia.com"], local: false }, + ] +} + #[cfg(test)] mod tests { use super::*;