diff --git a/src/smarttools/cli.py b/src/smarttools/cli.py index 307f300..c0a93a9 100644 --- a/src/smarttools/cli.py +++ b/src/smarttools/cli.py @@ -338,11 +338,148 @@ echo "input" | {args.name} return 0 +PROVIDER_INSTALL_INFO = { + "claude": { + "group": "Anthropic Claude", + "install_cmd": "npm install -g @anthropic-ai/claude-code", + "requires": "Node.js and npm", + "setup": "Run 'claude' and follow login prompts", + "cost": "Pay-per-use (API key required)", + "variants": ["claude", "claude-haiku", "claude-opus", "claude-sonnet"], + }, + "codex": { + "group": "OpenAI Codex", + "install_cmd": "pip install openai-codex", + "requires": "Python 3.8+", + "setup": "Set OPENAI_API_KEY environment variable", + "cost": "Pay-per-use (API key required)", + "variants": ["codex"], + }, + "gemini": { + "group": "Google Gemini", + "install_cmd": "pip install google-generativeai", + "requires": "Python 3.8+", + "setup": "Set GOOGLE_API_KEY or run 'gemini auth'", + "cost": "Free tier available, pay-per-use for more", + "variants": ["gemini", "gemini-flash"], + }, + "opencode": { + "group": "OpenCode", + "install_cmd": "curl -fsSL https://opencode.ai/install.sh | bash", + "requires": "curl, bash", + "setup": "Run 'opencode auth' to authenticate", + "cost": "Free tier (pickle), paid for other models", + "variants": ["opencode-deepseek", "opencode-pickle", "opencode-nano", "opencode-reasoner", "opencode-grok"], + }, + "ollama": { + "group": "Ollama (Local)", + "install_cmd": "curl -fsSL https://ollama.ai/install.sh | bash", + "requires": "curl, bash, decent GPU recommended", + "setup": "Run 'ollama pull llama3' to download a model", + "cost": "FREE (runs locally)", + "variants": [], + "custom": True, + }, +} + + def cmd_providers(args): """Manage AI providers.""" import shutil + import subprocess - if args.providers_cmd == "list": + if args.providers_cmd == "install": + print("=" * 60) + print("SmartTools Provider Installation Guide") + print("=" * 60) + print() + + # Check what's already installed + providers = load_providers() + installed_groups = set() + for p in providers: + if p.name.lower() == "mock": + continue + cmd_parts = p.command.split()[0] + cmd_expanded = cmd_parts.replace("$HOME", str(Path.home())).replace("~", str(Path.home())) + if shutil.which(cmd_expanded) or Path(cmd_expanded).exists(): + # Find which group this belongs to + for group, info in PROVIDER_INSTALL_INFO.items(): + if p.name in info.get("variants", []): + installed_groups.add(group) + + # Show available provider groups + print("Available AI Provider Groups:\n") + options = [] + for i, (key, info) in enumerate(PROVIDER_INSTALL_INFO.items(), 1): + status = "[INSTALLED]" if key in installed_groups else "" + print(f" {i}. {info['group']} {status}") + print(f" Cost: {info['cost']}") + print(f" Models: {', '.join(info['variants']) if info['variants'] else 'Custom'}") + print() + options.append(key) + + print(" 0. Cancel") + print() + + try: + choice = input("Select a provider to install (1-{}, or 0 to cancel): ".format(len(options))) + choice = int(choice) + except (ValueError, EOFError): + print("Cancelled.") + return 0 + + if choice == 0 or choice > len(options): + print("Cancelled.") + return 0 + + selected = options[choice - 1] + info = PROVIDER_INSTALL_INFO[selected] + + print() + print("=" * 60) + print(f"Installing: {info['group']}") + print("=" * 60) + print() + print(f"Requirements: {info['requires']}") + print(f"Install command: {info['install_cmd']}") + print(f"Post-install: {info['setup']}") + print() + + try: + confirm = input("Run installation command? (y/N): ").strip().lower() + except EOFError: + confirm = "n" + + if confirm == "y": + print() + print(f"Running: {info['install_cmd']}") + print("-" * 40) + result = subprocess.run(info['install_cmd'], shell=True) + print("-" * 40) + + if result.returncode == 0: + print() + print("Installation completed!") + print() + print(f"Next steps:") + print(f" 1. {info['setup']}") + print(f" 2. Test with: smarttools providers test {info['variants'][0] if info['variants'] else selected}") + else: + print() + print(f"Installation failed (exit code {result.returncode})") + print("Try running the command manually:") + print(f" {info['install_cmd']}") + else: + print() + print("To install manually, run:") + print(f" {info['install_cmd']}") + print() + print(f"Then: {info['setup']}") + + return 0 + + elif args.providers_cmd == "list": providers = load_providers() print(f"Configured providers ({len(providers)}):\n") for p in providers: @@ -520,6 +657,10 @@ def main(): p_prov_check = providers_sub.add_parser("check", help="Check which providers are available") p_prov_check.set_defaults(func=cmd_providers) + # providers install + p_prov_install = providers_sub.add_parser("install", help="Interactive guide to install AI providers") + p_prov_install.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")