refactor(hermes_cli): inline _try_poll, hoist datetime import, share _kv/_detail and _header/_lines
This commit is contained in:
@@ -204,10 +204,8 @@ def _print_cmd_rows(rows):
|
||||
|
||||
|
||||
def _print_section_header(title):
|
||||
print(_setup.color("─" * 60, _setup.Colors.DIM))
|
||||
print()
|
||||
print(_setup.color(title, _setup.Colors.CYAN, _setup.Colors.BOLD))
|
||||
print()
|
||||
print(_setup.color("─" * 60, _setup.Colors.DIM), end="\n\n")
|
||||
print(_setup.color(title, _setup.Colors.CYAN, _setup.Colors.BOLD), end="\n\n")
|
||||
|
||||
|
||||
def _print_setup_summary(config: dict, hermes_home):
|
||||
@@ -251,8 +249,7 @@ def _print_setup_summary(config: dict, hermes_home):
|
||||
for line in _DONE_BANNER:
|
||||
print(_setup.color(line, _setup.Colors.GREEN))
|
||||
print()
|
||||
print(_setup.color(f"📁 All your files are in {_dhh()}/:", _setup.Colors.CYAN, _setup.Colors.BOLD))
|
||||
print()
|
||||
print(_setup.color(f"📁 All your files are in {_dhh()}/:", _setup.Colors.CYAN, _setup.Colors.BOLD), end="\n\n")
|
||||
for label, value in (("Settings:", f" {_setup.get_config_path()}"), ("API Keys:", f" {_setup.get_env_path()}"),
|
||||
("Data:", f" {hermes_home}/cron/, sessions/, logs/")):
|
||||
print(f" {_setup.color(label, _setup.Colors.YELLOW)}{value}")
|
||||
@@ -262,9 +259,7 @@ def _print_setup_summary(config: dict, hermes_home):
|
||||
_print_cmd_rows(_EDIT_WIZARD_ROWS)
|
||||
print()
|
||||
_print_cmd_rows(_EDIT_CONFIG_ROWS)
|
||||
print(" Set a specific value")
|
||||
print()
|
||||
print(" Or edit the files directly:")
|
||||
print(" Set a specific value\n\n Or edit the files directly:")
|
||||
for path in (_setup.get_config_path(), _setup.get_env_path()):
|
||||
print(f" {_setup.color(f'nano {path}', _setup.Colors.DIM)}")
|
||||
print()
|
||||
|
||||
@@ -270,8 +270,7 @@ def _setup_backend_plugin(config: dict, backend: str) -> None:
|
||||
_BUILTIN_TERMINAL_BACKENDS = [
|
||||
("local", "Local - run directly on this machine (default)"),
|
||||
("docker", "Docker - isolated container with configurable resources"),
|
||||
("modal", "Modal - serverless cloud sandbox"),
|
||||
("ssh", "SSH - run on a remote machine"),
|
||||
("modal", "Modal - serverless cloud sandbox"), ("ssh", "SSH - run on a remote machine"),
|
||||
("daytona", "Daytona - persistent cloud development environment"),
|
||||
("vercel_sandbox", "Vercel Sandbox - cloud microVM with snapshot filesystem persistence")]
|
||||
_TERMINAL_BACKEND_SETUP = {
|
||||
|
||||
@@ -132,9 +132,7 @@ def _prompt_validated(
|
||||
# --- Wizard
|
||||
|
||||
def _header(title: str) -> None:
|
||||
print("─" * 50)
|
||||
print(title)
|
||||
print("─" * 50)
|
||||
_lines("─" * 50, title, "─" * 50)
|
||||
|
||||
|
||||
def _lines(*lines: str) -> None:
|
||||
|
||||
@@ -40,12 +40,12 @@ def _row(name: str, ok: bool, text: str, width: int = 12, sep: str = " ") -> No
|
||||
|
||||
def _detail(label: str, value) -> None:
|
||||
"""Print an indented ``label: value`` detail line under a status row."""
|
||||
print(f" {label:<12}{value}")
|
||||
_kv(label, value, " ", 12)
|
||||
|
||||
|
||||
def _kv(label: str, value) -> None:
|
||||
def _kv(label: str, value, indent: str = " ", width: int = 14) -> None:
|
||||
"""Print a `` Label: value`` line (label padded to the 14-col status layout)."""
|
||||
print(f" {label:<14}{value}")
|
||||
print(f"{indent}{label:<{width}}{value}")
|
||||
|
||||
|
||||
def _kv_flag(label: str, ok, on: str, off: str) -> None:
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
Origin helpers (``_row``, ``_first_env_value``, ...) are resolved through the ``hermes_cli.status``
|
||||
module object so tests that monkeypatch that module keep working."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from hermes_cli.auth import AuthError
|
||||
from hermes_cli.nous_account import (
|
||||
format_nous_portal_entitlement_message, get_nous_portal_account_info)
|
||||
@@ -14,7 +16,6 @@ def _format_iso_timestamp(value) -> str:
|
||||
text = value.strip() if isinstance(value, str) else ""
|
||||
if not text:
|
||||
return "(unknown)"
|
||||
from datetime import datetime, timezone
|
||||
try:
|
||||
parsed = datetime.fromisoformat(text[:-1] + "+00:00" if text.endswith("Z") else text)
|
||||
except Exception:
|
||||
@@ -25,7 +26,6 @@ def _format_iso_timestamp(value) -> str:
|
||||
|
||||
|
||||
def _qwen_expiry(expires_at_ms) -> str:
|
||||
from datetime import datetime, timezone
|
||||
return datetime.fromtimestamp(int(expires_at_ms) / 1000, tz=timezone.utc).isoformat()
|
||||
|
||||
|
||||
|
||||
@@ -84,8 +84,7 @@ def print_qr_code(url: str, *, include_link: bool = True) -> None:
|
||||
def create_pairing(
|
||||
api_url: str | None = None, bot_name: str = DEFAULT_BOT_NAME, timeout: float = 10.0
|
||||
) -> TelegramPairing | None:
|
||||
"""POST a pairing; returns deep link, QR payload, public id and the secret poll token (used
|
||||
only as a bearer credential while polling)."""
|
||||
"""POST a pairing; the returned poll token is only used as a bearer credential while polling."""
|
||||
try:
|
||||
resp = httpx.post(f"{_api_url(api_url)}/v1/telegram/pairings", json={"bot_name": bot_name}, timeout=timeout)
|
||||
if resp.status_code not in (200, 201):
|
||||
@@ -122,14 +121,6 @@ def poll_pairing_result_once(
|
||||
_parse_owner_user_id(data.get("owner_user_id")))
|
||||
|
||||
|
||||
def _try_poll(api_url: str | None, pairing: TelegramPairing) -> TelegramBotSetupResult | None:
|
||||
"""One poll attempt; transport/JSON errors count as 'not ready yet'."""
|
||||
try:
|
||||
return poll_pairing_result_once(api_url, pairing)
|
||||
except (httpx.HTTPError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def poll_for_setup_result(
|
||||
api_url: str | None, pairing: TelegramPairing, timeout: float = DEFAULT_POLL_TIMEOUT,
|
||||
interval: float = POLL_INTERVAL, on_tick=None) -> Optional[TelegramBotSetupResult]:
|
||||
@@ -140,8 +131,11 @@ def poll_for_setup_result(
|
||||
while time.monotonic() < deadline:
|
||||
if on_tick:
|
||||
on_tick(time.monotonic() - start)
|
||||
if result := _try_poll(api_url, pairing):
|
||||
return result
|
||||
try: # transport/JSON errors count as 'not ready yet'
|
||||
if result := poll_pairing_result_once(api_url, pairing):
|
||||
return result
|
||||
except (httpx.HTTPError, ValueError):
|
||||
pass
|
||||
time.sleep(interval)
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user