refactor(cli/setup.py): first-time mode table, quick-setup env-var helper, agent-settings summary table

This commit is contained in:
Teknium
2026-09-02 16:01:34 -07:00
parent 3ab00ea93a
commit 1ac48ebe5a
6 changed files with 114 additions and 181 deletions

View File

@@ -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 <section>`` 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)

View File

@@ -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

View File

@@ -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']}")

View File

@@ -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}")

View File

@@ -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)

View File

@@ -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: