Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/opensquilla/cli/agent_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,9 @@ def _write_json(path: str, payload: dict[str, Any]) -> None:

def _print_no_provider_error() -> None:
"""Print a three-section diagnostic panel when no LLM provider is configured."""
from opensquilla.onboarding.config_store import resolve_config_path

config_path, _ = resolve_config_path(os.environ.get("OPENSQUILLA_GATEWAY_CONFIG_PATH"))
body = Text.assemble(
("Symptom\n", "bold red"),
"No LLM provider configured.\n\n",
Expand All @@ -905,7 +908,7 @@ def _print_no_provider_error() -> None:
"No API key was found. The following environment variables were all empty:\n"
" OPENROUTER_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY,\n"
" DEEPSEEK_API_KEY, GEMINI_API_KEY, DASHSCOPE_API_KEY, and others.\n"
"The config file ~/.opensquilla/config.toml also has no [llm].api_key set.\n\n"
f"The config file {config_path} also has no [llm].api_key set.\n\n"
),
("Next steps\n", "bold green"),
(
Expand All @@ -915,7 +918,7 @@ def _print_no_provider_error() -> None:
" export OPENROUTER_API_KEY=sk-or-... # POSIX / macOS / Linux\n"
' setx OPENROUTER_API_KEY "sk-or-..." '
"# Windows cmd: set OPENROUTER_API_KEY=...\n\n"
"Option 3 — edit ~/.opensquilla/config.toml and add:\n"
f"Option 3 — edit {config_path} and add:\n"
" [llm]\n"
' api_key = "your-key-here"\n'
),
Expand Down
48 changes: 48 additions & 0 deletions tests/test_agent_cmd_no_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,51 @@ def test_exit_code_nonzero_when_no_key(
result = runner.invoke(app, ["-m", "hi"])

assert result.exit_code != 0


def _flattened_output(output: str) -> str:
"""Drop panel borders and whitespace so a long path that Rich wrapped still matches."""
return re.sub(r"[│\s]", "", output)


def test_panel_names_the_state_dir_config_file(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
"""With OPENSQUILLA_STATE_DIR set, the panel points at the config file the CLI reads."""
for key in _PROVIDER_ENV_VARS:
monkeypatch.delenv(key, raising=False)
monkeypatch.delenv("OPENSQUILLA_GATEWAY_CONFIG_PATH", raising=False)
monkeypatch.setenv("HOME", str(tmp_path / "home"))
monkeypatch.setenv("USERPROFILE", str(tmp_path / "home"))
monkeypatch.chdir(tmp_path)
state_dir = tmp_path / "state"
monkeypatch.setenv("OPENSQUILLA_STATE_DIR", str(state_dir))

with _mock_patch():
result = CliRunner().invoke(_make_app(), ["-m", "hi"])

combined = _flattened_output(result.output or "")
assert str(state_dir / "config.toml").replace(" ", "") in combined
assert "~/.opensquilla/config.toml" not in combined


def test_panel_names_the_explicit_config_file(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
"""With OPENSQUILLA_GATEWAY_CONFIG_PATH set, the panel points at that file."""
for key in _PROVIDER_ENV_VARS:
monkeypatch.delenv(key, raising=False)
monkeypatch.setenv("HOME", str(tmp_path / "home"))
monkeypatch.setenv("USERPROFILE", str(tmp_path / "home"))
monkeypatch.chdir(tmp_path)
config_file = tmp_path / "custom" / "my-config.toml"
monkeypatch.setenv("OPENSQUILLA_GATEWAY_CONFIG_PATH", str(config_file))

with _mock_patch():
result = CliRunner().invoke(_make_app(), ["-m", "hi"])

combined = _flattened_output(result.output or "")
assert str(config_file).replace(" ", "") in combined
assert "~/.opensquilla/config.toml" not in combined
Loading