diff --git a/src/openjarvis/cli/ask.py b/src/openjarvis/cli/ask.py index 9811f99a..d1c21075 100644 --- a/src/openjarvis/cli/ask.py +++ b/src/openjarvis/cli/ask.py @@ -8,6 +8,7 @@ import sys import click from rich.console import Console +from openjarvis.cli.hints import hint_no_engine from openjarvis.core.config import load_config from openjarvis.core.events import EventBus from openjarvis.core.types import Message, Role @@ -268,6 +269,7 @@ def ask( ) except EngineConnectionError as exc: console.print(f"[red]Engine error:[/red] {exc}") + console.print(hint_no_engine()) sys.exit(1) if output_json: diff --git a/src/openjarvis/cli/hints.py b/src/openjarvis/cli/hints.py new file mode 100644 index 00000000..41924b91 --- /dev/null +++ b/src/openjarvis/cli/hints.py @@ -0,0 +1,41 @@ +"""Rich-formatted error hints for common CLI failure modes.""" + +from __future__ import annotations + +from typing import Optional + + +def hint_no_config() -> str: + """Return a suggestion when no config file is found.""" + return ( + "[yellow]Hint:[/yellow] No config file found.\n" + " Run [bold]jarvis init[/bold] to detect hardware and generate " + "[cyan]~/.openjarvis/config.toml[/cyan].\n" + " Or run [bold]jarvis quickstart[/bold] for a guided setup." + ) + + +def hint_no_engine(engine_name: Optional[str] = None) -> str: + """Return a suggestion when the inference engine is unreachable.""" + name = engine_name or "ollama" + return ( + f"[yellow]Hint:[/yellow] Engine '{name}' is not reachable.\n" + f" Make sure the {name} server is running.\n" + " Run [bold]jarvis doctor[/bold] to check all engines.\n" + " Run [bold]jarvis quickstart[/bold] for guided setup." + ) + + +def hint_no_model(model_name: Optional[str] = None) -> str: + """Return a suggestion when no model is available.""" + if model_name: + return ( + f"[yellow]Hint:[/yellow] Model '{model_name}' not found.\n" + f" Try: [bold]ollama pull {model_name}[/bold]\n" + " Run [bold]jarvis model list[/bold] to see available models." + ) + return ( + "[yellow]Hint:[/yellow] No models available.\n" + " Pull a model first: [bold]ollama pull qwen3:8b[/bold]\n" + " Run [bold]jarvis model list[/bold] to see available models." + ) diff --git a/tests/cli/test_hints.py b/tests/cli/test_hints.py new file mode 100644 index 00000000..0ba4797a --- /dev/null +++ b/tests/cli/test_hints.py @@ -0,0 +1,37 @@ +"""Tests for CLI error hint functions.""" + +from __future__ import annotations + +from openjarvis.cli.hints import ( + hint_no_config, + hint_no_engine, + hint_no_model, +) + + +class TestHintFunctions: + def test_hint_no_config_returns_string(self): + msg = hint_no_config() + assert isinstance(msg, str) + assert len(msg) > 0 + assert "init" in msg.lower() or "config" in msg.lower() + + def test_hint_no_engine_returns_string(self): + msg = hint_no_engine() + assert isinstance(msg, str) + assert len(msg) > 0 + assert "engine" in msg.lower() or "ollama" in msg.lower() + + def test_hint_no_engine_with_name(self): + msg = hint_no_engine("vllm") + assert "vllm" in msg.lower() + + def test_hint_no_model_returns_string(self): + msg = hint_no_model() + assert isinstance(msg, str) + assert len(msg) > 0 + assert "model" in msg.lower() or "pull" in msg.lower() + + def test_hint_no_model_with_name(self): + msg = hint_no_model("qwen3:8b") + assert "qwen3:8b" in msg