diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 588d06d40..6bcd49ef6 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -233,6 +233,14 @@ PROVIDER_REGISTRY: Dict[str, ProviderConfig] = { api_key_env_vars=("HF_TOKEN",), base_url_env_var="HF_BASE_URL", ), + "ollama": ProviderConfig( + id="ollama", + name="Ollama (Local)", + auth_type="api_key", + inference_base_url="http://localhost:11434/v1", + api_key_env_vars=("OLLAMA_API_KEY",), + base_url_env_var="OLLAMA_BASE_URL", + ), } @@ -343,6 +351,15 @@ def _resolve_api_key_provider_secret( pass return "", "" + # Ollama does not require an API key — check env vars first, + # then fall back to a dummy token so credential checks pass. + if provider_id == "ollama": + for env_var in pconfig.api_key_env_vars: + val = os.getenv(env_var, "").strip() + if has_usable_secret(val): + return val, env_var + return "ollama", "default" + for env_var in pconfig.api_key_env_vars: val = os.getenv(env_var, "").strip() if has_usable_secret(val): @@ -781,7 +798,7 @@ def resolve_provider( "kilo": "kilocode", "kilo-code": "kilocode", "kilo-gateway": "kilocode", # Local server aliases — route through the generic custom provider "lmstudio": "custom", "lm-studio": "custom", "lm_studio": "custom", - "ollama": "custom", "vllm": "custom", "llamacpp": "custom", + "vllm": "custom", "llamacpp": "custom", "llama.cpp": "custom", "llama-cpp": "custom", } normalized = _PROVIDER_ALIASES.get(normalized, normalized) diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 1a968952a..3934adf79 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -4206,7 +4206,7 @@ For more help on a command: ) chat_parser.add_argument( "--provider", - choices=["auto", "openrouter", "nous", "openai-codex", "copilot-acp", "copilot", "anthropic", "gemini", "huggingface", "zai", "kimi-coding", "minimax", "minimax-cn", "kilocode"], + choices=["auto", "openrouter", "nous", "openai-codex", "copilot-acp", "copilot", "anthropic", "gemini", "huggingface", "zai", "kimi-coding", "minimax", "minimax-cn", "kilocode", "ollama"], default=None, help="Inference provider (default: auto)" ) diff --git a/hermes_cli/models.py b/hermes_cli/models.py index a5b1c2b2f..d244b51ed 100644 --- a/hermes_cli/models.py +++ b/hermes_cli/models.py @@ -263,6 +263,20 @@ _PROVIDER_MODELS: dict[str, list[str]] = { "XiaomiMiMo/MiMo-V2-Flash", "moonshotai/Kimi-K2-Thinking", ], + "ollama": [ + "gemma4", + "gemma4:27b", + "hermes3", + "hermes3:70b", + "hermes4", + "llama3.1", + "llama3.1:70b", + "qwen2.5-coder", + "qwen2.5-coder:32b", + "deepseek-r1", + "phi4", + "mistral", + ], } _PROVIDER_LABELS = { @@ -284,6 +298,7 @@ _PROVIDER_LABELS = { "kilocode": "Kilo Code", "alibaba": "Alibaba Cloud (DashScope)", "huggingface": "Hugging Face", + "ollama": "Ollama (Local)", "custom": "Custom endpoint", } @@ -568,7 +583,7 @@ def list_available_providers() -> list[dict[str, str]]: "gemini", "huggingface", "zai", "kimi-coding", "minimax", "minimax-cn", "kilocode", "anthropic", "alibaba", "opencode-zen", "opencode-go", - "ai-gateway", "deepseek", "custom", + "ai-gateway", "deepseek", "ollama", "custom", ] # Build reverse alias map aliases_for: dict[str, list[str]] = {} diff --git a/hermes_cli/providers.py b/hermes_cli/providers.py index 890927884..08b4c4ce8 100644 --- a/hermes_cli/providers.py +++ b/hermes_cli/providers.py @@ -122,6 +122,11 @@ HERMES_OVERLAYS: Dict[str, HermesOverlay] = { is_aggregator=True, base_url_env_var="HF_BASE_URL", ), + "ollama": HermesOverlay( + transport="openai_chat", + is_aggregator=False, + base_url_env_var="OLLAMA_BASE_URL", + ), } @@ -216,7 +221,7 @@ ALIASES: Dict[str, str] = { "lmstudio": "lmstudio", "lm-studio": "lmstudio", "lm_studio": "lmstudio", - "ollama": "ollama-cloud", + # ollama is now a first-class provider (issue #169) "vllm": "local", "llamacpp": "local", "llama.cpp": "local",