From 1ac48ebe5a79f7e1279bd0e7a596d3bc7710b561 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:01:34 -0700 Subject: [PATCH] refactor(cli/setup.py): first-time mode table, quick-setup env-var helper, agent-settings summary table --- hermes_cli/setup.py | 131 ++++++++++++++-------------------- hermes_cli/setup_migration.py | 19 +++-- hermes_cli/setup_quick.py | 112 ++++++++++------------------- hermes_cli/setup_summary.py | 5 +- hermes_cli/setup_terminal.py | 16 ++--- hermes_cli/setup_tts.py | 12 ++-- 6 files changed, 114 insertions(+), 181 deletions(-) diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index 8ee6e79924..b236149c88 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -306,8 +306,7 @@ def prompt_choice(question: str, choices: list, default: int = 0, description: s idx = _curses_prompt_choice(question, choices, default, description=description) if idx >= 0: if idx == default: - print_info(" Skipped (keeping current)") - print() + _info(" Skipped (keeping current)", None) return default print() return idx @@ -436,16 +435,14 @@ def setup_model_provider(config: dict, *, quick: bool = False): """ from hermes_cli.config import load_config, save_config print_header("Inference Provider") - print_info("Choose how to connect to your main chat model.") - print_info(f" Guide: {_DOCS_BASE}/integrations/providers") - print() + _info("Choose how to connect to your main chat model.", + f" Guide: {_DOCS_BASE}/integrations/providers", None) from hermes_cli.main import select_provider_and_model try: select_provider_and_model() except (SystemExit, KeyboardInterrupt): - print() - print_info("Provider setup skipped.") + _info(None, "Provider setup skipped.") except Exception as exc: logger.debug("select_provider_and_model error during setup: %s", exc) print_warning(f"Provider setup encountered an error: {exc}") @@ -525,14 +522,13 @@ def setup_agent_settings(config: dict): """Configure agent behavior: iterations, progress display, compression, session reset.""" print_header("Agent Settings") - print_info(f" Guide: {_DOCS_BASE}/user-guide/configuration") - print() + _info(f" Guide: {_DOCS_BASE}/user-guide/configuration", None) # ── Max Iterations ── (config.yaml is authoritative; never surface a stale legacy .env value) current_max = str(cfg_get(config, "agent", "max_turns", default=90)) - print_info("Maximum tool-calling iterations per conversation.") - print_info("Higher = more complex tasks, but costs more tokens.") - print_info(f"Press Enter to keep {current_max}. Use 90 for most tasks or 150+ for open exploration.") + _info("Maximum tool-calling iterations per conversation.", + "Higher = more complex tasks, but costs more tokens.", + f"Press Enter to keep {current_max}. Use 90 for most tasks or 150+ for open exploration.") try: max_iter = int(prompt("Max iterations", current_max)) @@ -561,8 +557,8 @@ def setup_agent_settings(config: dict): # ── Context Compression ── print_header("Context Compression") - print_info("Automatically summarizes old messages when context gets too long.") - print_info("Higher threshold = compress later (use more context). Lower = compress sooner.") + _info("Automatically summarizes old messages when context gets too long.", + "Higher threshold = compress later (use more context). Lower = compress sooner.") config.setdefault("compression", {})["enabled"] = True @@ -598,15 +594,15 @@ def setup_agent_settings(config: dict): if mode in ("both", "daily"): _prompt_int_setting(reset_cfg, "at_hour", " Daily reset hour (0-23, local time)", current_hour, lambda v: 0 <= v <= 23) idle_now, hour_now = reset_cfg.get("idle_minutes", 1440), reset_cfg.get("at_hour", 4) - if mode == "both": - print_success(f"Sessions reset after {idle_now} min idle or daily at {hour_now}:00") - elif mode == "idle": - print_success(f"Sessions reset after {idle_now} min of inactivity") - elif mode == "daily": - print_success(f"Sessions reset daily at {hour_now}:00") - elif mode == "none": + if mode == "none": print_info("Sessions will never auto-reset. Context is managed only by compression.") print_warning("Long conversations will grow in cost. Use /reset manually when needed.") + elif mode is not None: + print_success({ + "both": f"Sessions reset after {idle_now} min idle or daily at {hour_now}:00", + "idle": f"Sessions reset after {idle_now} min of inactivity", + "daily": f"Sessions reset daily at {hour_now}:00", + }[mode]) save_config(config) @@ -641,8 +637,8 @@ _SEND_CONSENT_EXPLAINER = ( def setup_telemetry(config: dict): """Configure the local shared-metrics subscriber and optional sending.""" print_header("Shared Metrics") - print_info("Shared metrics contain only bounded counters and histograms.") - print_info("Collection is local. Sending them to Nous is a separate opt-in.") + _info("Shared metrics contain only bounded counters and histograms.", + "Collection is local. Sending them to Nous is a separate opt-in.") telemetry = config.get("telemetry") if not isinstance(telemetry, dict): @@ -742,8 +738,7 @@ def run_setup_wizard(args): try: return _run_setup_wizard_impl(args) except _SetupCancelled: - print() - print_info("Setup cancelled. Remaining sections were not changed.") + _info(None, "Setup cancelled. Remaining sections were not changed.") return None @@ -779,12 +774,9 @@ def _run_setup_section(config: dict, section: str) -> None: def _run_full_setup(config: dict, hermes_home, *, is_existing: bool, migration_ran: bool) -> None: """Full Setup — run all sections, honoring post-migration skips.""" print_header("Configuration Location") - print_info(f"Config file: {get_config_path()}") - print_info(f"Secrets file: {get_env_path()}") - print_info(f"Data folder: {hermes_home}") - print_info(f"Install dir: {PROJECT_ROOT}") - print() - print_info("You can edit these files directly or use 'hermes config edit'") + _info(f"Config file: {get_config_path()}", f"Secrets file: {get_env_path()}", + f"Data folder: {hermes_home}", f"Install dir: {PROJECT_ROOT}", None, + "You can edit these files directly or use 'hermes config edit'") if migration_ran: _info(None, "Settings were imported from OpenClaw.", @@ -831,6 +823,16 @@ def _run_full_setup(config: dict, hermes_home, *, is_existing: bool, migration_r ) +# First-time mode picker: (menu label, runner) — a None runner falls through to Full Setup. +_FIRST_TIME_MODES = ( + ("Quick Setup (Nous Portal) — free OAuth login, no API keys, model + tools (recommended)", + lambda config, home, is_existing: _run_first_time_quick_setup(config, home, is_existing)), + ("Full setup — configure every provider, tool & option yourself (bring your own keys)", None), + ("Blank Slate — everything off except the bare minimum; opt in to each capability", + lambda config, home, is_existing: _run_blank_slate_setup(config, home, is_existing)), +) + + def _run_setup_wizard_impl(args): """Run the interactive setup wizard: full/quick (auto-detected), ``--portal``, or one ``hermes setup
`` from SETUP_SECTIONS.""" @@ -840,43 +842,35 @@ def _run_setup_wizard_impl(args): return ensure_hermes_home() - if bool(getattr(args, "reset", False)): + if getattr(args, "reset", False): save_config(copy.deepcopy(DEFAULT_CONFIG)) print_success("Configuration reset to defaults.") - reconfigure_requested = bool(getattr(args, "reconfigure", False)) quick_requested = bool(getattr(args, "quick", False)) config = load_config() hermes_home = get_hermes_home() - config_path = get_config_path() _backup_path = _backup_config_file(config_path) - # Detect non-interactive environments (headless SSH, Docker, CI/CD) + # Non-interactive environments (headless SSH, Docker, CI/CD) if getattr(args, 'non_interactive', False) or not is_interactive_stdin(): print_noninteractive_setup_guidance("Running in a non-interactive environment (no TTY detected).") return - - # --portal: one-shot Nous Portal setup. Skips the rest of the wizard. - if bool(getattr(args, "portal", False)): + if getattr(args, "portal", False): # one-shot Nous Portal setup; skips the rest _run_portal_one_shot(config) return - section = getattr(args, "section", None) if section: _run_setup_section(config, section) return - # Check if this is an existing installation with a provider configured + # Existing installation == a provider is configured from hermes_cli.auth import get_active_provider - is_existing = ( - bool(get_env_value("OPENROUTER_API_KEY")) - or bool(get_env_value("OPENAI_BASE_URL")) - or get_active_provider() is not None + is_existing = bool( + get_env_value("OPENROUTER_API_KEY") or get_env_value("OPENAI_BASE_URL") or get_active_provider() is not None ) - _print_banner( "│ ⚕ Hermes Agent Setup Wizard │", "├─────────────────────────────────────────────────────────┤", @@ -885,14 +879,13 @@ def _run_setup_wizard_impl(args): ) migration_ran = False - if is_existing: - # Existing install — the full reconfigure wizard is the default (Enter keeps each current - # value); `--quick` narrows it to missing items (partial OpenClaw import, cleared key). + # Full reconfigure wizard is the default (Enter keeps each current value); `--quick` + # narrows it to missing items (partial OpenClaw import, cleared key). --reconfigure is a + # backwards-compatible no-op here. if quick_requested: _run_setup_steps([("Quick Setup", lambda: _run_quick_setup(config, hermes_home))]) return - print() print_header("Reconfigure") print_success("You already have Hermes configured.") @@ -900,38 +893,18 @@ def _run_setup_wizard_impl(args): "Press Enter to keep it, or type a new value to change it.", "", "Tip: jump straight to a section with 'hermes setup model|terminal|", " gateway|tools|agent', or fill only missing items with --quick.") - # --reconfigure is kept for backwards compatibility and is a no-op here. else: - # ── First-Time Setup ── (--reconfigure / --quick are meaningless here; fall through) + # First-time setup (--reconfigure / --quick are meaningless here; fall through) print() if reconfigure_requested or quick_requested: - print_info("No existing configuration found — running first-time setup.") - print() - - # Offer OpenClaw migration before configuration begins - migration_ran = _offer_openclaw_migration(hermes_home) + _info("No existing configuration found — running first-time setup.", None) + migration_ran = _offer_openclaw_migration(hermes_home) # before configuration begins if migration_ran: config = load_config() - - setup_mode = prompt_choice( - "How would you like to set up Hermes?", - [ - "Quick Setup (Nous Portal) — free OAuth login, no API keys, model + tools (recommended)", - "Full setup — configure every provider, tool & option yourself (bring your own keys)", - "Blank Slate — everything off except the bare minimum; opt in to each capability", - ], - 0, - ) - - if setup_mode == 0: - _run_setup_steps( - [("Quick Setup", lambda: _run_first_time_quick_setup(config, hermes_home, is_existing))] - ) - return - if setup_mode == 2: - _run_setup_steps( - [("Blank Slate", lambda: _run_blank_slate_setup(config, hermes_home, is_existing))] - ) + setup_mode = prompt_choice("How would you like to set up Hermes?", [label for label, _ in _FIRST_TIME_MODES], 0) + label, runner = _FIRST_TIME_MODES[setup_mode] + if runner is not None: + _run_setup_steps([(label, lambda: runner(config, hermes_home, is_existing))]) return _run_full_setup(config, hermes_home, is_existing=is_existing, migration_ran=migration_ran) @@ -939,7 +912,7 @@ def _run_setup_wizard_impl(args): # Save and show summary save_config(config) if _backup_path and _backup_path.exists(): - print_info(f"Previous config backed up to: {_backup_path}") - print_info("If setup changed a value you customized, restore it with:") - print_info(f" cp {_backup_path} {config_path}") + _info(f"Previous config backed up to: {_backup_path}", + "If setup changed a value you customized, restore it with:", + f" cp {_backup_path} {config_path}") _print_setup_summary(config, hermes_home) diff --git a/hermes_cli/setup_migration.py b/hermes_cli/setup_migration.py index 2bf36273cf..44943e4565 100644 --- a/hermes_cli/setup_migration.py +++ b/hermes_cli/setup_migration.py @@ -274,8 +274,8 @@ def _offer_openclaw_migration(hermes_home: Path) -> bool: successfully, False otherwise. """ from hermes_cli.setup import ( - _OPENCLAW_SCRIPT, get_config_path, load_config, print_header, print_info, - print_success, print_warning, prompt_yes_no, save_config, + _OPENCLAW_SCRIPT, get_config_path, _info, load_config, print_header, print_info, print_success, + print_warning, prompt_yes_no, save_config, ) openclaw_dir = Path.home() / ".openclaw" if not openclaw_dir.is_dir() or not _OPENCLAW_SCRIPT.exists(): @@ -283,9 +283,8 @@ def _offer_openclaw_migration(hermes_home: Path) -> bool: print() print_header("OpenClaw Installation Detected") - print_info(f"Found OpenClaw data at {openclaw_dir}") - print_info("Hermes can preview what would be imported before making any changes.") - print() + _info(f"Found OpenClaw data at {openclaw_dir}", + "Hermes can preview what would be imported before making any changes.", None) if not prompt_yes_no("Would you like to see what can be imported?", default=True): print_info("Skipping migration. You can run it later with: hermes claw migrate --dry-run") return False @@ -315,19 +314,17 @@ def _offer_openclaw_migration(hermes_home: Path) -> bool: preview_count = preview_report.get("summary", {}).get("migrated", 0) if preview_count == 0: - print() - print_info("Nothing to import from OpenClaw.") + _info(None, "Nothing to import from OpenClaw.") return False print() print_header(f"Migration Preview — {preview_count} item(s) would be imported") - print_info("No changes have been made yet. Review the list below:") - print() + _info("No changes have been made yet. Review the list below:", None) _print_migration_preview(preview_report) # ── Phase 2: Confirm and execute ── if not prompt_yes_no("Proceed with migration?", default=False): - print_info("Migration cancelled. You can run it later with: hermes claw migrate") - print_info("Use --dry-run to preview again, or --preset minimal for a lighter import.") + _info("Migration cancelled. You can run it later with: hermes claw migrate", + "Use --dry-run to preview again, or --preset minimal for a lighter import.") return False # overwrite=False so existing Hermes configs are preserved. The user saw the diff --git a/hermes_cli/setup_quick.py b/hermes_cli/setup_quick.py index 0c3ebc9921..d79a186db6 100644 --- a/hermes_cli/setup_quick.py +++ b/hermes_cli/setup_quick.py @@ -23,20 +23,23 @@ def _reload_config_into(config: dict) -> None: config.update(_refreshed) -def _prompt_env_var(var: dict): - """Prompt for one env-var value (masked when the var is a secret).""" - from hermes_cli.setup import prompt - return prompt(f" {var.get('prompt', var['name'])}", password=bool(var.get("password"))) +def _prompt_and_save_env_var(var: dict, saved_msg: str, skipped_msg: str) -> None: + """Prompt for one env-var value (masked when secret); persist and confirm, or report the skip.""" + from hermes_cli.setup import print_success, print_warning, prompt, save_env_value + value = prompt(f" {var.get('prompt', var['name'])}", password=bool(var.get("password"))) + if value: + save_env_value(var["name"], value) + print_success(saved_msg) + else: + print_warning(skipped_msg) def _run_portal_one_shot(config: dict) -> None: - """One-shot Nous Portal setup — OAuth + model pick + provider + Tool Gateway. + """One-shot Nous Portal setup (``hermes setup --portal`` / ``hermes portal``). - Wired into ``hermes setup --portal`` and ``hermes portal``: the Nous-Portal slice - of first-time quick setup as one shareable command. Login, model selection, - provider switch and Tool Gateway opt-in are all delegated to ``_model_flow_nous`` - — the same flow ``_run_first_time_quick_setup`` and ``hermes model`` use for Nous — - so there is a single source of truth and ``hermes portal`` always offers a picker. + Login, model pick, provider switch and Tool Gateway opt-in are all delegated to + ``_model_flow_nous`` — the same flow quick setup and ``hermes model`` use for Nous — so + there is one source of truth and ``hermes portal`` always offers a picker. """ from hermes_cli.setup import Colors, color, _info, load_config, print_error, print_info, print_success print() @@ -48,16 +51,12 @@ def _run_portal_one_shot(config: dict) -> None: " — all routed through your Nous Portal sub.", None, " Sign up: https://portal.nousresearch.com/manage-subscription", None) - # Handles both the logged-out path (device-code OAuth, selects a model) and the - # logged-in path (curated picker), then Tool Gateway opt-in; sets provider=nous. try: from hermes_cli.main import _model_flow_nous - _model_flow_nous(config) except (KeyboardInterrupt, EOFError, SystemExit): - # _login_nous raises SystemExit(130)/(1) on cancel/failure; the expired- - # session re-login path inside _model_flow_nous only catches Exception, - # so a SystemExit there would otherwise escape and kill the whole CLI. + # _login_nous raises SystemExit(130)/(1) on cancel/failure; the expired-session re-login + # path inside _model_flow_nous only catches Exception, so SystemExit would kill the CLI. _info(None, " Setup cancelled.", " You can retry later with `hermes portal`.") return except Exception as exc: @@ -67,8 +66,7 @@ def _run_portal_one_shot(config: dict) -> None: print_info(" You can retry later with `hermes portal`.") return - # Re-sync from disk — _model_flow_nous writes via its own load/save cycle, so - # a later save_config(config) by a caller must not clobber those values. + # Re-sync from disk so a caller's later save_config(config) can't clobber the login save. try: _refreshed = load_config() if isinstance(_refreshed, dict): @@ -79,8 +77,7 @@ def _run_portal_one_shot(config: dict) -> None: print() print_success("Portal setup complete.") - print_info(" Run `hermes portal info` to inspect routing.") - print_info(" Run `hermes` to start chatting.") + _info(" Run `hermes portal info` to inspect routing.", " Run `hermes` to start chatting.") def _run_first_time_quick_setup(config: dict, hermes_home, is_existing: bool): @@ -104,8 +101,7 @@ def _run_first_time_quick_setup(config: dict, hermes_home, is_existing: bool): from hermes_cli.main import _model_flow_nous _model_flow_nous(config) except (KeyboardInterrupt, EOFError): - print() - print_info("Nous Portal setup cancelled.") + _info(None, "Nous Portal setup cancelled.") except Exception as exc: logger.debug("_model_flow_nous error during quick setup: %s", exc) print_warning(f"Nous Portal setup encountered an error: {exc}") @@ -137,8 +133,7 @@ def _run_first_time_quick_setup(config: dict, hermes_home, is_existing: bool): print() print_success("Setup complete! You're ready to go.") - print() - print_info(" Configure all settings: hermes setup") + _info(None, " Configure all settings: hermes setup") if gateway_choice != 0: print_info(" Connect Telegram/Discord: hermes setup gateway") _print_macos_fda_tip() @@ -379,8 +374,7 @@ def _blank_slate_walkthrough(config: dict, hermes_home): def _run_quick_setup(config: dict, hermes_home): """Quick setup — only configure items that are missing.""" from hermes_cli.setup import ( - Colors, color, _info, print_header, print_info, _print_setup_summary, print_success, print_warning, - _prompt_api_key, prompt_checklist, save_config, save_env_value, + Colors, color, _info, print_header, print_info, _print_setup_summary, print_success, _prompt_api_key, prompt_checklist, save_config, ) from hermes_cli.config import (get_missing_env_vars, get_missing_config_fields, check_config_version) print() @@ -398,94 +392,66 @@ def _run_quick_setup(config: dict, hermes_home): "or pick a specific section from the menu.") return - # Handle missing required env vars if missing_required: - print() - print_info(f"{len(missing_required)} required setting(s) missing:") + _info(None, f"{len(missing_required)} required setting(s) missing:") for var in missing_required: print(f" • {var['name']}") print() - for var in missing_required: print() print(color(f" {var['name']}", Colors.CYAN)) print_info(f" {var.get('description', '')}") if var.get("url"): print_info(f" Get key at: {var['url']}") + _prompt_and_save_env_var(var, f" Saved {var['name']}", f" Skipped {var['name']}") - value = _prompt_env_var(var) - if value: - save_env_value(var["name"], value) - print_success(f" Saved {var['name']}") - else: - print_warning(f" Skipped {var['name']}") - - # Split missing optional vars by category missing_tools = [v for v in missing_optional if v.get("category") == "tool"] missing_messaging = [ v for v in missing_optional if v.get("category") == "messaging" and not v.get("advanced") ] - # Tool API keys (checklist) - if missing_tools: + if missing_tools: # checklist, then the API-key screen for each pick print() print_header("Tool API Keys") - - checklist_labels = [] + labels = [] for var in missing_tools: tools = var.get("tools", []) tools_str = f" → {', '.join(tools[:2])}" if tools else "" - checklist_labels.append(f"{var.get('description', var['name'])}{tools_str}") - - for idx in prompt_checklist("Which tools would you like to configure?", checklist_labels): + labels.append(f"{var.get('description', var['name'])}{tools_str}") + for idx in prompt_checklist("Which tools would you like to configure?", labels): _prompt_api_key(missing_tools[idx]) - # Messaging platforms (checklist then prompt for selected) - if missing_messaging: + if missing_messaging: # checklist, then prompt for each selected platform's vars print() print_header("Messaging Platforms") - print_info("Connect Hermes to messaging apps to chat from anywhere.") - print_info("You can configure these later with 'hermes setup gateway'.") - - # Group by platform (preserving order); vars matching no platform are dropped. - platform_order = [] - platforms = {} + _info("Connect Hermes to messaging apps to chat from anywhere.", + "You can configure these later with 'hermes setup gateway'.") + # Group by platform in first-seen order; vars matching no platform are dropped. + grouped: dict[str, list] = {} emojis = {} for var in missing_messaging: for needle, plat, emoji in _MESSAGING_PLATFORMS: if needle in var["name"]: + grouped.setdefault(plat, []).append(var) + emojis[plat] = emoji break - else: - continue - if plat not in platforms: - platform_order.append(plat) - emojis[plat] = emoji - platforms.setdefault(plat, []).append(var) - - platform_labels = [f"{emojis[p]} {p}" for p in platform_order] - selected_indices = prompt_checklist("Which platforms would you like to set up?", platform_labels) - - for idx in selected_indices: + platform_order = list(grouped) + labels = [f"{emojis[p]} {p}" for p in platform_order] + for idx in prompt_checklist("Which platforms would you like to set up?", labels): plat = platform_order[idx] print() print(color(f" ─── {emojis[plat]} {plat} ───", Colors.CYAN)) print() - for var in platforms[plat]: + for var in grouped[plat]: print_info(f" {var.get('description', '')}") if var.get("url"): print_info(f" {var['url']}") - value = _prompt_env_var(var) - if value: - save_env_value(var["name"], value) - print_success(" ✓ Saved") - else: - print_warning(" Skipped") + _prompt_and_save_env_var(var, " ✓ Saved", " Skipped") print() # Handle missing config fields if missing_config: - print() - print_info(f"Adding {len(missing_config)} new config option(s) with defaults...") + _info(None, f"Adding {len(missing_config)} new config option(s) with defaults...") for field in missing_config: print_success(f" Added {field['key']} = {field['default']}") diff --git a/hermes_cli/setup_summary.py b/hermes_cli/setup_summary.py index 53aefc9d52..7ae2b6f8e9 100644 --- a/hermes_cli/setup_summary.py +++ b/hermes_cli/setup_summary.py @@ -256,7 +256,7 @@ def _print_setup_summary(config: dict, hermes_home): """Print the setup completion summary.""" from hermes_cli.setup import ( Colors, color, get_config_path, get_env_path, get_nous_subscription_features, _info, print_header, - print_info, print_warning, + print_warning, ) # Provider readiness — the one thing setup absolutely must produce. Previously a user # could cancel the API-key prompt mid-wizard (Enter → "Cancelled."), watch the wizard @@ -289,8 +289,7 @@ def _print_setup_summary(config: dict, hermes_home): tool_status.append(row) available_count = sum(1 for _, avail, _ in tool_status if avail) - print_info(f"{available_count}/{len(tool_status)} tool categories available:") - print() + _info(f"{available_count}/{len(tool_status)} tool categories available:", None) for name, available, missing_var in tool_status: if available: print(f" {color('✓', Colors.GREEN)} {name}") diff --git a/hermes_cli/setup_terminal.py b/hermes_cli/setup_terminal.py index 99e78a7c8f..6d98cf0d23 100644 --- a/hermes_cli/setup_terminal.py +++ b/hermes_cli/setup_terminal.py @@ -238,10 +238,10 @@ def _setup_backend_daytona(config: dict) -> None: def _setup_backend_vercel(config: dict) -> None: - from hermes_cli.setup import print_info, print_success, print_warning + from hermes_cli.setup import _info, print_info, print_success, print_warning print_success("Terminal backend: Vercel Sandbox") - print_info("Cloud microVM sandboxes with snapshot-backed filesystem persistence.") - print_info("Requires the optional SDK: pip install 'hermes-agent[vercel]'") + _info("Cloud microVM sandboxes with snapshot-backed filesystem persistence.", + "Requires the optional SDK: pip install 'hermes-agent[vercel]'") try: __import__("vercel") except ImportError: @@ -338,14 +338,14 @@ _TERMINAL_BACKEND_SETUP = { def setup_terminal_backend(config: dict): """Configure the terminal execution backend.""" from hermes_cli.setup import ( - _DOCS_BASE, cfg_get, print_header, print_info, print_success, prompt_choice, save_config, save_env_value, + _DOCS_BASE, cfg_get, _info, print_header, print_info, print_success, prompt_choice, save_config, + save_env_value, ) import platform as _platform print_header("Terminal Backend") - print_info("Choose where Hermes runs shell commands and code.") - print_info("This affects tool execution, file access, and isolation.") - print_info(f" Guide: {_DOCS_BASE}/user-guide/configuration#terminal-backend-configuration") - print() + _info("Choose where Hermes runs shell commands and code.", + "This affects tool execution, file access, and isolation.", + f" Guide: {_DOCS_BASE}/user-guide/configuration#terminal-backend-configuration", None) current_backend = cfg_get(config, "terminal", "backend", default="local") backends = list(_BUILTIN_TERMINAL_BACKENDS) diff --git a/hermes_cli/setup_tts.py b/hermes_cli/setup_tts.py index 76f14a2589..26d82f35eb 100644 --- a/hermes_cli/setup_tts.py +++ b/hermes_cli/setup_tts.py @@ -106,7 +106,7 @@ def _run_xai_oauth_login_from_setup() -> bool: Returns True on success, False on any failure (the caller falls back to whatever the user picked next, e.g. Edge TTS). """ - from hermes_cli.setup import print_info, print_warning + from hermes_cli.setup import _info, print_warning try: from hermes_cli.auth import ( _is_remote_session, _save_xai_oauth_tokens, _xai_oauth_device_code_login, @@ -117,8 +117,7 @@ def _run_xai_oauth_login_from_setup() -> bool: return False open_browser = not _is_remote_session() - print() - print_info("Signing in to xAI Grok OAuth (SuperGrok / Premium+)...") + _info(None, "Signing in to xAI Grok OAuth (SuperGrok / Premium+)...") try: creds = _xai_oauth_device_code_login(open_browser=open_browser) _save_xai_oauth_tokens( @@ -276,16 +275,15 @@ def _tts_xai_step(config: dict) -> str: def _setup_tts_provider(config: dict): """Interactive TTS provider selection with install flow for local engines.""" from hermes_cli.setup import ( - get_env_value, get_nous_subscription_features, managed_nous_tools_enabled, print_header, print_info, - print_success, print_warning, prompt_choice, save_config, + get_env_value, get_nous_subscription_features, _info, managed_nous_tools_enabled, print_header, + print_info, print_success, print_warning, prompt_choice, save_config, ) current_provider = config.get("tts", {}).get("provider", "edge") current_label = _TTS_PROVIDER_LABELS.get(current_provider, current_provider) print() print_header("Text-to-Speech Provider (optional)") - print_info(f"Current: {current_label}") - print() + _info(f"Current: {current_label}", None) options = list(_TTS_PROVIDER_CHOICES) if managed_nous_tools_enabled() and get_nous_subscription_features(config).nous_auth_present: