f48eb5a05f
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / quality (pull_request) Successful in 19s
CI / build (pull_request) Successful in 24s
CI / security (pull_request) Successful in 34s
CI / typecheck (pull_request) Successful in 43s
CI / integration_tests (pull_request) Successful in 2m37s
CI / unit_tests (pull_request) Successful in 6m12s
CI / docker (pull_request) Successful in 1m1s
CI / benchmark-regression (pull_request) Successful in 15m33s
CI / coverage (pull_request) Successful in 21m54s
Implement `agents repl` command providing an interactive read-eval-print loop that dispatches to existing CLI commands without the `agents` prefix. Features: - Command dispatch via existing Typer app - readline history with --no-history opt-out (default: ~/.cleveragents/history) - Tab-completion for all CLI commands and built-in commands - !! last-command repetition - Prompt context from CLEVERAGENTS_PROJECT / CLEVERAGENTS_PLAN env vars - Ctrl+C (continue) and Ctrl+D (exit) signal handling - Multi-line input with \ continuation - :help and :exit/:quit built-in commands Test coverage: - 15 Behave scenarios (features/repl.feature) - 10 Robot Framework smoke tests (robot/repl_smoke.robot) - ASV benchmark suite (benchmarks/repl_bench.py) - All nox sessions pass: lint, typecheck, unit_tests, integration_tests - Coverage: 97.2% (threshold: 97%)
144 lines
4.2 KiB
Python
144 lines
4.2 KiB
Python
"""Helper script for repl_smoke.robot smoke tests.
|
|
|
|
Each function is a self-contained check used as a Robot Framework keyword.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
from io import StringIO
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
# Ensure local source tree is importable
|
|
_SRC = str(Path(__file__).resolve().parents[1] / "src")
|
|
if _SRC not in sys.path:
|
|
sys.path.insert(0, _SRC)
|
|
|
|
from cleveragents.cli.commands.repl import ( # noqa: E402
|
|
_get_prompt_context,
|
|
_setup_completer,
|
|
dispatch_command,
|
|
run_repl,
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fake input helper
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class _FakeInput:
|
|
"""Feed predetermined lines then raise ``EOFError``."""
|
|
|
|
def __init__(self, lines: list[str]) -> None:
|
|
self._lines = list(lines)
|
|
self._idx = 0
|
|
|
|
def __call__(self, prompt: str = "") -> str:
|
|
if self._idx >= len(self._lines):
|
|
raise EOFError
|
|
line = self._lines[self._idx]
|
|
self._idx += 1
|
|
return line
|
|
|
|
|
|
def _run(lines: list[str]) -> tuple[int, str]:
|
|
from rich.console import Console
|
|
|
|
buf = StringIO()
|
|
console = Console(file=buf, no_color=True, width=200)
|
|
fake = _FakeInput(lines)
|
|
with (
|
|
patch("cleveragents.cli.commands.repl._console", console),
|
|
patch("builtins.input", fake),
|
|
):
|
|
code = run_repl(no_history=True)
|
|
return code, buf.getvalue()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Robot keywords
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def get_default_prompt() -> str:
|
|
"""Return prompt context with no environment variables set."""
|
|
old_proj = os.environ.pop("CLEVERAGENTS_PROJECT", None)
|
|
old_plan = os.environ.pop("CLEVERAGENTS_PLAN", None)
|
|
try:
|
|
return _get_prompt_context()
|
|
finally:
|
|
if old_proj is not None:
|
|
os.environ["CLEVERAGENTS_PROJECT"] = old_proj
|
|
if old_plan is not None:
|
|
os.environ["CLEVERAGENTS_PLAN"] = old_plan
|
|
|
|
|
|
def get_prompt_with_project(project: str) -> str:
|
|
"""Return prompt context with CLEVERAGENTS_PROJECT set."""
|
|
old = os.environ.get("CLEVERAGENTS_PROJECT")
|
|
os.environ["CLEVERAGENTS_PROJECT"] = project
|
|
try:
|
|
return _get_prompt_context()
|
|
finally:
|
|
if old is None:
|
|
os.environ.pop("CLEVERAGENTS_PROJECT", None)
|
|
else:
|
|
os.environ["CLEVERAGENTS_PROJECT"] = old
|
|
|
|
|
|
def get_prompt_with_project_and_plan(project: str, plan: str) -> str:
|
|
"""Return prompt context with both env vars set."""
|
|
old_proj = os.environ.get("CLEVERAGENTS_PROJECT")
|
|
old_plan = os.environ.get("CLEVERAGENTS_PLAN")
|
|
os.environ["CLEVERAGENTS_PROJECT"] = project
|
|
os.environ["CLEVERAGENTS_PLAN"] = plan
|
|
try:
|
|
return _get_prompt_context()
|
|
finally:
|
|
if old_proj is None:
|
|
os.environ.pop("CLEVERAGENTS_PROJECT", None)
|
|
else:
|
|
os.environ["CLEVERAGENTS_PROJECT"] = old_proj
|
|
if old_plan is None:
|
|
os.environ.pop("CLEVERAGENTS_PLAN", None)
|
|
else:
|
|
os.environ["CLEVERAGENTS_PLAN"] = old_plan
|
|
|
|
|
|
def dispatch_argv(*args: str) -> int:
|
|
"""Call ``dispatch_command`` with the given argv tokens."""
|
|
return dispatch_command(list(args))
|
|
|
|
|
|
def run_repl_with_eof() -> int:
|
|
"""Run the REPL with no input (immediate EOF)."""
|
|
code, _ = _run([])
|
|
return code
|
|
|
|
|
|
def run_repl_with_help() -> str:
|
|
"""Run the REPL, send ``:help`` then exit, return output."""
|
|
_, output = _run([":help"])
|
|
return output
|
|
|
|
|
|
def run_repl_with_bang_bang() -> str:
|
|
"""Run the REPL, send ``!!`` with no prior command, return output."""
|
|
_, output = _run(["!!"])
|
|
return output
|
|
|
|
|
|
def verify_completer() -> bool:
|
|
"""Verify that the tab completer is installed and recognises commands."""
|
|
import readline
|
|
|
|
_setup_completer()
|
|
# The completer should match ':help' when given ':'
|
|
completer_func = readline.get_completer()
|
|
if completer_func is None:
|
|
return False
|
|
match = completer_func(":", 0)
|
|
return match is not None and match.startswith(":")
|