From 6abca22019543ad20fe185783c623fa5dd8f5f00 Mon Sep 17 00:00:00 2001 From: rob Date: Mon, 29 Dec 2025 12:53:43 -0400 Subject: [PATCH] feat: add providers CLI command for managing AI providers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New commands: - smarttools providers list - List all providers with status - smarttools providers check - Check which providers are available - smarttools providers add - Add/update provider - smarttools providers remove - Remove provider - smarttools providers test - Test a provider Features: - Shows [+] for available, [-] for missing providers - Mock provider always shows as available (built-in) - Helpful installation hints when no providers found - Can add custom providers (Ollama, local scripts, etc.) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/smarttools/cli.py | 135 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/src/smarttools/cli.py b/src/smarttools/cli.py index 0d14001..307f300 100644 --- a/src/smarttools/cli.py +++ b/src/smarttools/cli.py @@ -2,10 +2,12 @@ import argparse import sys +from pathlib import Path from . import __version__ from .tool import list_tools, load_tool, save_tool, delete_tool, Tool, ToolArgument, PromptStep, CodeStep from .ui import run_ui +from .providers import load_providers, add_provider, delete_provider, Provider, call_provider def cmd_list(args): @@ -336,6 +338,107 @@ echo "input" | {args.name} return 0 +def cmd_providers(args): + """Manage AI providers.""" + import shutil + + if args.providers_cmd == "list": + providers = load_providers() + print(f"Configured providers ({len(providers)}):\n") + for p in providers: + # Mock provider is always available + if p.name.lower() == "mock": + print(f" [+] {p.name}") + print(f" Command: (built-in)") + print(f" Status: OK (always available)") + if p.description: + print(f" Info: {p.description}") + print() + continue + + # Check if command exists + cmd_parts = p.command.split()[0] + cmd_expanded = cmd_parts.replace("$HOME", str(Path.home())).replace("~", str(Path.home())) + exists = shutil.which(cmd_expanded) is not None or Path(cmd_expanded).exists() + status = "OK" if exists else "NOT FOUND" + status_icon = "+" if exists else "-" + + print(f" [{status_icon}] {p.name}") + print(f" Command: {p.command}") + print(f" Status: {status}") + if p.description: + print(f" Info: {p.description}") + print() + return 0 + + elif args.providers_cmd == "add": + name = args.name + command = args.command + description = args.description or "" + + provider = Provider(name, command, description) + add_provider(provider) + print(f"Provider '{name}' added/updated.") + return 0 + + elif args.providers_cmd == "remove": + name = args.name + if delete_provider(name): + print(f"Provider '{name}' removed.") + return 0 + else: + print(f"Provider '{name}' not found.") + return 1 + + elif args.providers_cmd == "test": + name = args.name + print(f"Testing provider '{name}'...") + result = call_provider(name, "Say 'hello' and nothing else.", timeout=30) + if result.success: + print(f"SUCCESS: {result.text[:200]}...") + else: + print(f"FAILED: {result.error}") + return 0 if result.success else 1 + + elif args.providers_cmd == "check": + providers = load_providers() + print("Checking all providers...\n") + available = [] + missing = [] + + for p in providers: + # Mock provider is always available (handled specially) + if p.name.lower() == "mock": + available.append(p.name) + print(f" [+] {p.name}: OK (built-in)") + continue + + cmd_parts = p.command.split()[0] + cmd_expanded = cmd_parts.replace("$HOME", str(Path.home())).replace("~", str(Path.home())) + exists = shutil.which(cmd_expanded) is not None or Path(cmd_expanded).exists() + + if exists: + available.append(p.name) + print(f" [+] {p.name}: OK") + else: + missing.append(p.name) + print(f" [-] {p.name}: NOT FOUND ({cmd_parts})") + + print(f"\nSummary: {len(available)} available, {len(missing)} missing") + if len(available) == 1 and available[0] == "mock": + print(f"\nNo real AI providers found. Install one of these:") + print(f" - claude: npm install -g @anthropic-ai/claude-cli") + print(f" - codex: pip install openai-codex") + print(f" - gemini: pip install google-generative-ai") + print(f"\nMeanwhile, use mock provider for testing:") + print(f" echo 'test' | summarize --provider mock") + elif missing: + print(f"\nAvailable providers: {', '.join(available)}") + return 0 + + return 0 + + def main(): """Main CLI entry point.""" parser = argparse.ArgumentParser( @@ -405,6 +508,38 @@ def main(): p_docs.add_argument("-e", "--edit", action="store_true", help="Edit/create README in $EDITOR") p_docs.set_defaults(func=cmd_docs) + # 'providers' command + p_providers = subparsers.add_parser("providers", help="Manage AI providers") + providers_sub = p_providers.add_subparsers(dest="providers_cmd", help="Provider commands") + + # providers list + p_prov_list = providers_sub.add_parser("list", help="List all providers and their status") + p_prov_list.set_defaults(func=cmd_providers) + + # providers check + p_prov_check = providers_sub.add_parser("check", help="Check which providers are available") + p_prov_check.set_defaults(func=cmd_providers) + + # providers add + p_prov_add = providers_sub.add_parser("add", help="Add or update a provider") + p_prov_add.add_argument("name", help="Provider name") + p_prov_add.add_argument("command", help="Command to run (e.g., 'claude -p')") + p_prov_add.add_argument("-d", "--description", help="Provider description") + p_prov_add.set_defaults(func=cmd_providers) + + # providers remove + p_prov_remove = providers_sub.add_parser("remove", help="Remove a provider") + p_prov_remove.add_argument("name", help="Provider name") + p_prov_remove.set_defaults(func=cmd_providers) + + # providers test + p_prov_test = providers_sub.add_parser("test", help="Test a provider") + p_prov_test.add_argument("name", help="Provider name") + p_prov_test.set_defaults(func=cmd_providers) + + # Default for providers with no subcommand + p_providers.set_defaults(func=lambda args: cmd_providers(args) if args.providers_cmd else (setattr(args, 'providers_cmd', 'list') or cmd_providers(args))) + args = parser.parse_args() # If no command, launch UI