Skip to content
Merged
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
21 changes: 17 additions & 4 deletions backend/openbot_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Commands mirror the core web-UI flows against a running OpenBot server:

openbot connect <url> [--api-key KEY] Save connection settings
openbot connect <url> [--api-key KEY] Save + verify connection settings
openbot threads [list] List threads
openbot thread <id> [watch] Show a thread (or live-follow it)
openbot send <thread> "<msg>" [--bot @h] Post a message, optionally @-ing a bot
Expand All @@ -19,10 +19,11 @@
import json
import os
import sys
from pathlib import Path

import click

from openbot_cli.client import GitHubIssuesClient, OpenBotClient
from openbot_cli.client import GitHubIssuesClient, OpenBotClient, OpenBotClientError
from openbot_cli.config import (
CONFIG_PATH,
ConfigError,
Expand Down Expand Up @@ -69,15 +70,27 @@ def cli(ctx: click.Context, base_url: str | None, api_key: str | None) -> None:
@click.option("--api-key", default=None, help="API key to store alongside the URL.")
@click.option("--config", "config_path", default=None, type=click.Path(), help="Config file (advanced).")
def connect(url: str | None, api_key: str | None, config_path: str | None) -> None:
"""Save the OpenBot server URL (and optional API key) for future commands."""
path = CONFIG_PATH if config_path is None else __import__("pathlib").Path(config_path)
"""Save the OpenBot server URL (and optional API key) and verify it responds.

Persists the connection to the config file, then probes the server's public
``/api/v1/health`` route so a bad URL or key surfaces immediately. A failed
probe is only a warning — the config is still saved so the user can retry.
"""
path = CONFIG_PATH if config_path is None else Path(config_path)
data = load_config(path)
if url:
data["base_url"] = url.rstrip("/")
if api_key:
data["api_key"] = api_key
saved = save_config(data, path)
click.echo(f"Saved connection to {data['base_url']} ({saved})")
probe = OpenBotClient(base_url=data["base_url"], api_key=data.get("api_key"))
try:
probe.ping()
except OpenBotClientError as e:
click.echo(f"Warning: could not reach the server: {e}", err=True)
return
click.echo("Connection OK (/api/v1/health)")


@cli.command("threads")
Expand Down
46 changes: 46 additions & 0 deletions backend/tests/test_openbot_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,52 @@ def handler(req):
assert r2.exit_code == 0 and "r1" in r2.output


def test_cli_connect_saves_and_pings(tmp_path, monkeypatch):
cfg = tmp_path / "cfg.json"
captured = {}

class _FakeClient:
def __init__(self, base_url, api_key=None):
captured["base_url"] = base_url
captured["api_key"] = api_key

def ping(self):
captured["pinged"] = True
return {"status": "ok"}

monkeypatch.setattr(cli_mod, "OpenBotClient", _FakeClient)
runner = CliRunner()
result = runner.invoke(cli_mod.cli, ["connect", "https://example.test", "--api-key", "sek", "--config", str(cfg)])
assert result.exit_code == 0, result.output
assert "Saved connection" in result.output
assert "Connection OK" in result.output
assert captured["base_url"] == "https://example.test"
assert captured["api_key"] == "sek"
assert captured["pinged"] is True
saved = json.loads(cfg.read_text())
assert saved["base_url"] == "https://example.test"
assert saved["api_key"] == "sek"


def test_cli_connect_warns_when_server_unreachable(tmp_path, monkeypatch):
cfg = tmp_path / "cfg.json"

class _FakeClient:
def __init__(self, base_url, api_key=None):
pass

def ping(self):
raise OpenBotClientError("GET /api/v1/health -> 500")

monkeypatch.setattr(cli_mod, "OpenBotClient", _FakeClient)
runner = CliRunner()
result = runner.invoke(cli_mod.cli, ["connect", "https://down.test", "--config", str(cfg)])
assert result.exit_code == 0, result.output
assert "Warning:" in result.output
# Config is still saved even though the probe failed.
assert json.loads(cfg.read_text())["base_url"] == "https://down.test"


def test_cli_issues_fetches_github(monkeypatch):
class FakeResp:
status_code = 200
Expand Down
Loading