Files
Auto-Trader/agent/cli/components/hint_bar.py
T
Haozhe Wu b5a44a1e56 refactor(cli): split agent/cli.py into agent/cli/ package
Restructures the 3216-line agent/cli.py into a focused package
while preserving every existing surface verbatim.

Legacy compatibility:
- _legacy.py shim keeps every subcommand (serve, run, hypothesis,
  alpha, memory, provider, swarm, sessions, ...) reachable through
  the same CLI flags and console-script entry.
- Public symbols (cmd_*, _INIT_ENV_PATH, Confirm, AGENT_DIR, ...)
  re-exported through cli.<name>; `from cli import *` exposes 76
  symbols (was 1).
- Package-level cli.RUNS_DIR / cli.SESSIONS_DIR monkeypatches now
  propagate into _legacy before any cmd_* runs.

New scaffolding:
- agent/cli/main.py interactive REPL front door
- agent/cli/commands/ slash router + handlers (15 commands)
- agent/cli/components/ reusable Rich UI building blocks
- agent/cli/{theme,intro,onboard,input,completer,stream}.py
- agent/cli/__main__.py restores `python -m cli`
- agent/cli/_version.py reads importlib.metadata.version() so
  --version output and the banner stay aligned with pyproject.toml

Production wiring:
- New FastAPI middleware in api_server.py serves
  frontend/dist/index.html on browser navigation to /runs/{id}
  and /correlation, regex-narrowed so /runs/{id}/code and
  /runs/{id}/pine API routes stay reachable.
- frontend/vite.config.ts dev proxy mirrors the same narrowing.
- scripts/dev backend starts via package-safe
  `python -c 'import cli; cli.main(...)'`.

Chat-gate narrowed: chat --help / chat -h / chat --max-iter <bad> /
chat extra correctly reach legacy argparse instead of being
swallowed by the new REPL.

Tests: 2271 passed, 43 skipped (focused CLI/SPA test files:
test_cli_interactive.py 67 + test_spa_deep_link.py 13 + existing
CLI memory/init/hypothesis 48 = 128).
2026-05-22 00:00:47 +08:00

68 lines
2.2 KiB
Python

"""Bottom hint bar — left + right aligned.
Renders a single-line hint where the right-aligned segment hugs the
terminal edge regardless of left-segment width. Mirrors dexter's
``hint-bar.ts``.
Used to surface keyboard guidance ("Tab to complete · /help for
commands") and ephemeral state ("input cleared", "cancelled").
"""
from __future__ import annotations
import shutil
from rich.text import Text
def _terminal_width(default: int = 80) -> int:
"""Best-effort terminal width detection (falls back gracefully)."""
try:
size = shutil.get_terminal_size((default, 20))
return max(20, size.columns)
except (OSError, ValueError):
return default
def render_hint_bar(left: str, right: str = "", *, width: int | None = None) -> Text:
"""Return a Rich :class:`Text` row with ``left`` and right-aligned ``right``.
The bar always fits the terminal width:
* If ``left + right`` fits, pad with spaces between them
* Otherwise truncate the left segment with an ellipsis so the
right hint remains visible (right-side hint usually carries
the *current* Ctrl+C semantics — losing it is worse than
losing context)
Args:
left: Left segment (e.g. ``"↑/↓ navigate · Tab/⏎ select"``).
right: Right segment (e.g. ``"Esc to cancel"``); may be empty.
width: Override terminal width. Defaults to autodetect.
"""
cols = width if width is not None else _terminal_width()
if right == "":
# Only a left hint — clip and pad to width, dim style throughout.
text = Text(left[:cols], style="dim")
return text
# Reserve at least one space between left and right segments.
available_for_left = cols - len(right) - 1
if available_for_left < 1:
# Right segment alone is wider than the terminal — show right only.
return Text(right[: cols].rjust(cols), style="dim")
if len(left) > available_for_left:
left = left[: max(1, available_for_left - 1)].rstrip() + "…"
padding = cols - len(left) - len(right)
if padding < 1:
padding = 1
bar = Text()
bar.append(left, style="dim")
bar.append(" " * padding)
bar.append(right, style="dim")
return bar
__all__ = ["render_hint_bar"]