Skip to content

feat: add OpenBot terminal client (openbot_cli package) - #314

Merged
regnull merged 1 commit into
mainfrom
feat/terminal-client
Oct 29, 2026
Merged

regnull merged 1 commit into
mainfrom
feat/terminal-client

Conversation

@regnull

@regnull regnull commented Oct 29, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a terminal client for OpenBot — a command-line frontend that mirrors the core web-UI flows against a running OpenBot server using the existing /api/v1 REST + SSE endpoints. Ships as the openbot console script. This is a frontend-only alternative to the React web UI; it does not invent a new protocol and does not touch server code.

Commands

  • openbot connect <url> [--api-key KEY] — save connection settings
  • openbot threads [list] — list threads
  • openbot thread <id> [watch] — show a thread (or live-follow SSE events)
  • openbot send <thread> "<msg>" [--bot @handle ...] — post a message, optionally @-ing a bot
  • openbot bots — list bots
  • openbot runs [<thread>] — list runs
  • openbot issues [--repo regnull/openbot] — list GitHub issues (via the public GitHub REST API; the server has no issues endpoint)

Package layout (backend/openbot_cli/)

  • client.py — OpenBotClient (threads / messages / bots / runs / health / SSE) + GitHubIssuesClient (no live server dependency)
  • cli.py — click group; the callback is injection-friendly (skips config-resolve/client-build when a client is already in ctx.obj, so tests/DI can pass a mock)
  • config.py — config discovery + persistence (~/.openbot/client.json or $OPENBOT_URL / $OPENBOT_API_KEY)
  • formatting.py — readable, color-light output helpers (+ format_ts with datetime.UTC, no timezone.utc)
  • README.md — install + usage guide (per repo standing guidance: every tool ships with a usage guide, not a one-liner)

Tests

backend/tests/test_openbot_cli.py — 15 tests, fully mocked via httpx.MockTransport (no live server). Covers client request/parse logic, the SSE stream path (wrapped in SyncByteStream to satisfy httpx's response.stream assertion), and every CLI command with an injected client. Naming note: the injected httpx.Client must carry base_url so relative paths resolve (the same way OpenBotClient configures the client it builds itself).

Acceptance criteria

  1. ✅ List threads + read a thread's messages from a running server (threads, thread <id>)
  2. ✅ Post a message, address a bot with --bot, and stream its reply via thread <id> watch
  3. ✅ bots, runs, issues return parsed, readable output
  4. ✅ Tests pass + CI gate green: uv run ruff check . clean, full backend suite 856 passed / 30 deselected, no regressions to web UI
  5. ✅ README/usage section documents install + commands

Notes / constraints honored

  • Built entirely inside the feat/terminal-client worktree. The pre-existing untracked backend/openbot/cli_client.py and the unrelated WIP (delivery.py, pyproject.toml, test_actor_system.py) in the main checkout are intentionally left untouched.
  • No new protocol: reuses /api/v1/threads, /threads/{id}, /threads/{id}/messages, /bots, /runs, /runs/{id}, /health, SSE /events, and the GitHub REST API for issues.
  • ruff format deliberately not run (not part of CI; would churn the tree).

A command-line frontend that mirrors the core web-UI flows against a running
OpenBot server via the existing /api/v1 REST + SSE endpoints. Ships as the
`openbot` console script.

Commands: connect, threads, thread [watch], send [--bot], bots, runs, issues.

- client.py: OpenBotClient (threads/messages/bots/runs/health/SSE) + GitHubIssuesClient
- cli.py: click group with injection-friendly callback (tests can pass a mock client)
- config.py / formatting.py: config persistence + readable output helpers
- README.md: install + usage guide (per repo 'ship every tool with a usage guide')
- tests/test_openbot_cli.py: full suite mocked via httpx.MockTransport, no live server

Tests: 15 new, full backend suite green (856 passed). Ruff clean.
Note: built in the terminal-client worktree; the pre-existing untracked
backend/openbot/cli_client.py + WIP in the main checkout are intentionally
untouched.

@regnull regnull left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ PASS — Ready to merge (COMMENTED review)

Self-approval is blocked on the shared account (GitHub: "Review cannot approve your own pull request"), so this is a COMMENTED verdict, not an approval. CI is green on b712afa (verified by @chief_of_staff; not re-checked here).

Scope

Diff is exactly: backend/openbot_cli/ (new package: __init__.py, cli.py, client.py, config.py, formatting.py, README.md), backend/tests/test_openbot_cli.py, backend/pyproject.toml (+5 additive), backend/uv.lock. The unrelated WIP (cli_client.py, runtime/delivery.py, test_actor_system.py) is absent from the diff — only runtime/delivery.py appears as a docstring reference inside client.py prose, not as a change. Shared-workspace discipline held.

Acceptance criteria — verified against code

  • AC1 (threads + history): threads → GET /api/v1/threads (cli.py:222, client.py:457); thread <id> → GET /api/v1/threads/{id}?limit= (cli.py:242, client.py:460). ✅
  • AC2 (send + --bot + watch): send → POST /api/v1/threads/{id}/messages with to[], --bot repeatable and strips @ (cli.py:280-301, client.py:474-484); thread <id> watch → stream_events over GET /api/v1/events?thread_id= SSE, renders message.created live (cli.py:258-277, client.py:500-534). ✅
  • AC3 (bots/runs/issues): bots→GET /api/v1/bots, runs→GET /api/v1/runs (client.py:488-496); issues → GitHubIssuesClient hits https://api.github.com/repos/{repo}/issues directly — no backend issues endpoint assumed (cli.py:345-360, client.py:566-588). X-API-Key header applied elsewhere (client.py:431-432, 507-508). ✅
  • AC4 (tests/ruff/no web regressions): 15 new tests (9 client + 6 CLI) match the count; testpaths=["tests"] so they run in the suite. No web-UI/templates files touched. Ruff-clean per green CI. ✅
  • AC5 (README): documents Install, Connect, command table, examples, streaming, auth, testing. ✅

Priority targets — all confirmed real (not test hacks)

  1. DI seam: cli skips resolve/build when ctx.obj["client"] present (if "client" in ctx.obj: return, cli.py:193-196) — a genuine injection seam; tests inject the client via CliRunner(obj=...). ✅
  2. SSE mock: _SS(SyncByteStream) wraps the byte iterator (test:837-844); stream_events parses real data: lines + event: (client.py:518-533); test asserts {"event":"message.created","id":"m1"}. ✅
  3. CliRunner args fix: helper forwards the command list directly to runner.invoke(cli, args) instead of nesting it in a tuple — commented as the unhashable type: 'list' guard (test:1019-1028). ✅
  4. GitHubIssuesClient: correct REST URL, filters PRs in issues command; test asserts url ends /repos/regnull/openbot/issues (test:985-1003). ✅
  5. pyproject.toml: purely additive — +click>=8.1 dep and [project.scripts] openbot = "openbot_cli.cli:cli". No WIP-touching lines. ✅

⚠️ NITs (non-blocking, carried forward)

  • README:91-92 says connect uses an unauthenticated /health probe "for a connection test," but connect only saves config (cli.py:210-219) — no probe runs. ping() exists and is unit-tested (test_health_probe_is_public_path) but no CLI command invokes it. Doc/capability drift, not a code defect.
  • Minor: connect uses __import__("pathlib") inline (cli.py:212) instead of a top-level import; harmless but inconsistent with the rest of the module.

No blockers. Hand to @qa for the combined test+merge gate (squash + delete-branch, human approval gate).

OpenBot - @reviewer

@regnull
regnull marked this pull request as ready for review October 29, 2026 17:34
@regnull
regnull merged commit 0bda7e3 into main Oct 29, 2026
4 checks passed
@regnull
regnull deleted the feat/terminal-client branch October 29, 2026 17:34
regnull added a commit that referenced this pull request Oct 29, 2026
…lib import

Two carried-forward nits from the terminal-client work (#314):

- `connect` only saved config; it now builds an OpenBotClient from the saved
  settings and probes the server's public `/api/v1/health` route, so a bad URL
  or key surfaces immediately. A failed probe is a warning, not fatal — the
  config is still saved so the user can retry (matches `ping()` which already
  existed and was tested but unwired).
- Replace `__import__("pathlib").Path(...)` with a top-level
  `from pathlib import Path` import.

Tests:
- test_cli_connect_saves_and_pings: asserts config is saved, client is built
  with the right base_url/key, ping() is called, and output reports OK.
- test_cli_connect_warns_when_server_unreachable: asserts the warning path and
  that config is still saved on a failed probe.

ruff clean on the openbot_cli package; 17 CLI tests + 50 (cli+config) pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant