refactor(cli/setup.py): shared _info() line printer; pack print_info runs across setup modules

This commit is contained in:
Teknium
2026-09-02 15:57:06 -07:00
parent 4e64c5d1a1
commit 3ab00ea93a
6 changed files with 98 additions and 137 deletions

View File

@@ -48,6 +48,12 @@ from hermes_cli.cli_output import print_error, print_info, print_success, print_
from hermes_cli.secret_prompt import masked_secret_prompt # noqa: E402
def _info(*lines: str | None) -> None:
"""print_info each line in order; ``None`` emits a bare blank ``print()``."""
for line in lines:
print() if line is None else print_info(line)
def _current_reasoning_effort(config: dict) -> str:
agent_cfg = config.get("agent")
if isinstance(agent_cfg, dict):
@@ -81,16 +87,13 @@ def print_noninteractive_setup_guidance(reason: str | None = None) -> None:
print()
if reason:
print_info(reason)
print_info("The interactive wizard cannot be used here.")
print()
print_info("Configure Hermes using environment variables or config commands:")
print_info(" hermes config set model.provider custom")
print_info(" hermes config set model.base_url http://localhost:8080/v1")
print_info(" hermes config set model.default your-model-name")
print()
print_info("Or set OPENROUTER_API_KEY / OPENAI_API_KEY in your environment.")
print_info("Run 'hermes setup' in an interactive terminal to use the full wizard.")
print()
_info("The interactive wizard cannot be used here.", None,
"Configure Hermes using environment variables or config commands:",
" hermes config set model.provider custom",
" hermes config set model.base_url http://localhost:8080/v1",
" hermes config set model.default your-model-name", None,
"Or set OPENROUTER_API_KEY / OPENAI_API_KEY in your environment.",
"Run 'hermes setup' in an interactive terminal to use the full wizard.", None)
_BRACKETED_PASTE_PATTERN = re.compile(r"\x1b\[\s*200~|\x1b\[\s*201~")
@@ -473,11 +476,9 @@ def _apply_default_agent_settings(config: dict):
save_config(config)
print_success("Applied recommended defaults:")
print_info(" Max iterations: 150")
print_info(" Tool progress: all")
print_info(" Compression threshold: 0.50")
print_info(" Session reset: never (use /reset or compression)")
print_info(" Run `hermes setup agent` later to customize.")
_info(" Max iterations: 150", " Tool progress: all", " Compression threshold: 0.50",
" Session reset: never (use /reset or compression)",
" Run `hermes setup agent` later to customize.")
def _prompt_int_setting(section: dict, key: str, label: str, current, accept) -> None:
@@ -786,10 +787,9 @@ def _run_full_setup(config: dict, hermes_home, *, is_existing: bool, migration_r
print_info("You can edit these files directly or use 'hermes config edit'")
if migration_ran:
print()
print_info("Settings were imported from OpenClaw.")
print_info("Each section below will show what was imported — press Enter to keep,")
print_info("or choose to reconfigure if needed.")
_info(None, "Settings were imported from OpenClaw.",
"Each section below will show what was imported — press Enter to keep,",
"or choose to reconfigure if needed.")
# Agent Settings are not prompted: first installs get defaults, existing keep theirs.
if not is_existing:
@@ -896,11 +896,10 @@ def _run_setup_wizard_impl(args):
print()
print_header("Reconfigure")
print_success("You already have Hermes configured.")
print_info("Running the full wizard — each prompt shows your current value.")
print_info("Press Enter to keep it, or type a new value to change it.")
print_info("")
print_info("Tip: jump straight to a section with 'hermes setup model|terminal|")
print_info(" gateway|tools|agent', or fill only missing items with --quick.")
_info("Running the full wizard — each prompt shows your current value.",
"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)

View File

@@ -15,13 +15,6 @@ def _is_valid_telegram_bot_token(token: str) -> bool:
return bool(_TELEGRAM_BOT_TOKEN_RE.match(token))
def _info(*lines: str | None) -> None:
"""print_info each line in order; ``None`` emits a bare blank ``print()``."""
from hermes_cli.setup import print_info
for line in lines:
print() if line is None else print_info(line)
def _profile_name_from_hermes_home(hermes_home) -> str | None:
"""Return the active profile name when HERMES_HOME is a profile dir."""
if hermes_home.parent.name == "profiles":
@@ -108,7 +101,7 @@ def _telegram_allowlist_nudge() -> None:
def _obtain_telegram_token():
"""Return (token, setup_result); auto flow first when chosen, else manual paste."""
from hermes_cli.setup import print_error, prompt
from hermes_cli.setup import _info, print_error, prompt
_info("How would you like to create your Telegram bot?", None,
" [1] Automatic (recommended)",
" Scan a QR code → confirm in Telegram → done.",
@@ -139,7 +132,7 @@ def _obtain_telegram_token():
def _setup_telegram():
"""Configure Telegram bot credentials and allowlist."""
from hermes_cli.setup import print_header, print_info, print_success, prompt, prompt_yes_no
from hermes_cli.setup import save_env_value
from hermes_cli.setup import _info, save_env_value
print_header("Telegram")
if _declines_reconfigure("TELEGRAM_BOT_TOKEN", "Telegram", "Reconfigure Telegram?"):
_telegram_allowlist_nudge()
@@ -205,7 +198,7 @@ def _setup_telegram():
def _setup_bluebubbles():
"""Configure BlueBubbles iMessage gateway."""
from hermes_cli.setup import print_header, print_info, print_success, print_warning, prompt
from hermes_cli.setup import prompt_yes_no, save_env_value
from hermes_cli.setup import _info, prompt_yes_no, save_env_value
print_header("BlueBubbles (iMessage)")
if _declines_reconfigure("BLUEBUBBLES_SERVER_URL", "BlueBubbles", "Reconfigure BlueBubbles?"):
return
@@ -252,7 +245,7 @@ def _setup_bluebubbles():
def _setup_webhooks():
"""Configure webhook integration."""
from hermes_cli.setup import print_header, print_success, print_warning, prompt, save_env_value
from hermes_cli.setup import _info, print_header, print_success, print_warning, prompt, save_env_value
print_header("Webhooks")
if _declines_reconfigure("WEBHOOK_ENABLED", "Webhooks", "Reconfigure webhooks?"):
return
@@ -308,7 +301,7 @@ def _is_progress(status: str) -> bool:
def _warn_missing_home_channels() -> None:
"""Platforms with a token but no home channel."""
from hermes_cli.setup import get_env_value, print_warning
from hermes_cli.setup import get_env_value, _info, print_warning
missing_home = [
plat for plat, token_var, home_vars in _HOME_CHANNEL_CHECKS
if get_env_value(token_var) and not any(get_env_value(v) for v in home_vars)
@@ -362,7 +355,7 @@ def _restart_running_gateway(any_messaging: bool, supports_systemd: bool) -> Non
def setup_gateway(config: dict):
"""Configure messaging platform integrations."""
from hermes_cli.setup import print_header, print_info, print_success, prompt_checklist
from hermes_cli.setup import _info, print_header, print_info, print_success, prompt_checklist
from hermes_cli.gateway import _all_platforms, _platform_status, _configure_platform
print_header("Messaging Platforms")
_info("Connect to messaging platforms to chat with Hermes from anywhere.",

View File

@@ -38,18 +38,15 @@ def _run_portal_one_shot(config: dict) -> None:
— 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.
"""
from hermes_cli.setup import Colors, color, load_config, print_error, print_info, print_success
from hermes_cli.setup import Colors, color, _info, load_config, print_error, print_info, print_success
print()
print(color("┌─────────────────────────────────────────────────────────┐", Colors.MAGENTA))
print(color("│ ⚕ Hermes Setup — Nous Portal (one-shot) │", Colors.MAGENTA))
print(color("└─────────────────────────────────────────────────────────┘", Colors.MAGENTA))
print()
print_info(" One subscription, 300+ models, plus the Tool Gateway:")
print_info(" web search, image generation, TTS, browser automation")
print_info(" — all routed through your Nous Portal sub.")
print()
print_info(" Sign up: https://portal.nousresearch.com/manage-subscription")
print()
_info(None, " One subscription, 300+ models, plus the Tool Gateway:",
" web search, image generation, TTS, browser automation",
" — 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.
@@ -61,9 +58,7 @@ def _run_portal_one_shot(config: dict) -> None:
# _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.
print()
print_info(" Setup cancelled.")
print_info(" You can retry later with `hermes portal`.")
_info(None, " Setup cancelled.", " You can retry later with `hermes portal`.")
return
except Exception as exc:
logger.debug("_model_flow_nous error during `hermes portal`: %s", exc)
@@ -95,17 +90,16 @@ def _run_first_time_quick_setup(config: dict, hermes_home, is_existing: bool):
<section>`` or switch providers with ``hermes model``.
"""
from hermes_cli.setup import (
_apply_default_agent_settings, _print_macos_fda_tip, _print_setup_summary, print_header,
print_info, print_success, print_warning, prompt_choice, save_config, setup_gateway,
_apply_default_agent_settings, _info, print_header, print_info, _print_macos_fda_tip,
_print_setup_summary, print_success, print_warning, prompt_choice, save_config, setup_gateway,
setup_terminal_backend,
)
# Step 1: Nous Portal — OAuth login + model selection (provider set to "nous" by the save).
print()
print_header("Nous Portal")
print_info("One subscription, 300+ models, plus the Tool Gateway:")
print_info(" web search, image generation, TTS, browser automation.")
print_info("Sign up: https://portal.nousresearch.com/manage-subscription")
print()
_info("One subscription, 300+ models, plus the Tool Gateway:",
" web search, image generation, TTS, browser automation.",
"Sign up: https://portal.nousresearch.com/manage-subscription", None)
try:
from hermes_cli.main import _model_flow_nous
_model_flow_nous(config)
@@ -161,7 +155,7 @@ def _print_macos_fda_tip() -> None:
(the TCC db dir is FDA-gated but probing it never triggers a dialog).
Silent on non-macOS and when FDA is already granted or indeterminate.
"""
from hermes_cli.setup import print_info
from hermes_cli.setup import _info
if sys.platform != "darwin":
return
tcc_dir = Path.home() / "Library" / "Application Support" / "com.apple.TCC"
@@ -172,12 +166,11 @@ def _print_macos_fda_tip() -> None:
pass
except OSError:
return # indeterminate — don't nag
print()
print_info(" macOS tip: silence ALL folder permission prompts with one switch —")
print_info(" System Settings → Privacy & Security → Full Disk Access → enable")
print_info(" your terminal (and Hermes.app if you use Desktop), or run:")
print_info(" open \"x-apple.systempreferences:com.apple.preference" ".security?Privacy_AllFiles\"")
print_info(" The grant is permanent — it survives every Hermes update.")
_info(None, " macOS tip: silence ALL folder permission prompts with one switch —",
" System Settings → Privacy & Security → Full Disk Access → enable",
" your terminal (and Hermes.app if you use Desktop), or run:",
" open \"x-apple.systempreferences:com.apple.preference" ".security?Privacy_AllFiles\"",
" The grant is permanent — it survives every Hermes update.")
def _blank_slate_minimal_toolsets(config: dict):
@@ -240,22 +233,20 @@ def _run_blank_slate_setup(config: dict, hermes_home, is_existing: bool):
else OFF; then either finish now or walk through opting capabilities back in.
Nothing is enabled that the user did not explicitly choose."""
from hermes_cli.setup import (
_blank_slate_minimal_toolsets, _blank_slate_minimize_config, _blank_slate_walkthrough,
_print_setup_summary, print_header, print_info, print_success, prompt_choice, save_config,
_blank_slate_minimal_toolsets, _blank_slate_minimize_config, _blank_slate_walkthrough, _info,
print_header, print_info, _print_setup_summary, print_success, prompt_choice, save_config,
setup_model_provider, setup_terminal_backend,
)
print()
print_header("Blank Slate Setup")
print_info("Everything starts OFF. First we force-enable only what's required")
print_info("to run an agent, then you choose whether to stop there or walk")
print_info("through enabling more — opting in to exactly what you want.")
print_info("")
print_info("Forced on: Provider & Model, File Operations, Terminal, Vision, Skills.")
print_info("Everything else (web, browser, code exec, memory,")
print_info("delegation, cron, plugins, MCP, …) starts disabled. The")
print_info("essential `hermes-agent` skill is always kept so the agent")
print_info("can help you drive and configure Hermes itself.")
print()
_info("Everything starts OFF. First we force-enable only what's required",
"to run an agent, then you choose whether to stop there or walk",
"through enabling more — opting in to exactly what you want.", "",
"Forced on: Provider & Model, File Operations, Terminal, Vision, Skills.",
"Everything else (web, browser, code exec, memory,",
"delegation, cron, plugins, MCP, …) starts disabled. The",
"essential `hermes-agent` skill is always kept so the agent",
"can help you drive and configure Hermes itself.", None)
# Step 1: Provider & Model (REQUIRED — the agent cannot run without it)
print_header("Step 1 — Provider & Model (required)")
@@ -296,13 +287,9 @@ def _run_blank_slate_setup(config: dict, hermes_home, is_existing: bool):
logger.debug("blank-slate skill opt-out error: %s", exc)
print()
print_success("Blank Slate setup complete — minimal agent ready.")
print_info("Enable anything later, on demand:")
print_info(" Enable tools: hermes tools")
print_info(" Seed skills: hermes skills opt-in --sync")
print_info(" Add MCP servers: hermes mcp add")
print_info(" Enable plugins: hermes plugins")
print_info(" Tune agent settings: hermes setup agent")
print()
_info("Enable anything later, on demand:", " Enable tools: hermes tools",
" Seed skills: hermes skills opt-in --sync", " Add MCP servers: hermes mcp add",
" Enable plugins: hermes plugins", " Tune agent settings: hermes setup agent", None)
_print_setup_summary(config, hermes_home)
return
@@ -312,8 +299,8 @@ def _run_blank_slate_setup(config: dict, hermes_home, is_existing: bool):
def _blank_slate_walkthrough(config: dict, hermes_home):
"""Opt-in walkthrough for Blank Slate: skills, tools, plugins, MCP, gateway."""
from hermes_cli.setup import (
_print_setup_summary, print_header, print_info, print_success, print_warning,
prompt_yes_no, save_config, setup_gateway,
_info, print_header, print_info, _print_setup_summary, print_success, print_warning, prompt_yes_no,
save_config, setup_gateway,
)
# Bundled skills — default to NONE, offer to seed all
print()
@@ -334,10 +321,10 @@ def _blank_slate_walkthrough(config: dict, hermes_home):
set_bundled_skills_opt_out(True)
# Essential skills (`hermes-agent`) are still seeded for an opted-out profile.
sync_skills(quiet=True)
print_info("No skills seeded (except the essential `hermes-agent`")
print_info("skill). A .no-bundled-skills marker keeps future")
print_info("`hermes update` runs from re-injecting them. Opt back in any")
print_info("time with `hermes skills opt-in --sync`.")
_info("No skills seeded (except the essential `hermes-agent`",
"skill). A .no-bundled-skills marker keeps future",
"`hermes update` runs from re-injecting them. Opt back in any",
"time with `hermes skills opt-in --sync`.")
except Exception as exc:
logger.debug("blank-slate skill handling error: %s", exc)
print_warning(f"Skill setup step encountered an error: {exc}")
@@ -345,9 +332,8 @@ def _blank_slate_walkthrough(config: dict, hermes_home):
# Walk through enabling additional tools
print()
print_header("Tools")
print_info("Pick exactly which additional toolsets to turn on.")
print_info("(file and terminal are already on; leave the rest off if you want")
print_info(" the most minimal agent.)")
_info("Pick exactly which additional toolsets to turn on.",
"(file and terminal are already on; leave the rest off if you want", " the most minimal agent.)")
if prompt_yes_no("Open the tool selector to enable more tools?", default=False):
try:
from hermes_cli.tools_config import tools_command
@@ -384,11 +370,8 @@ def _blank_slate_walkthrough(config: dict, hermes_home):
print()
print_success("Blank Slate setup complete — minimal agent ready.")
print_info(" Enable more tools: hermes tools")
print_info(" Seed skills: hermes skills opt-in --sync")
print_info(" Add MCP servers: hermes mcp add")
print_info(" Tune agent settings: hermes setup agent")
print()
_info(" Enable more tools: hermes tools", " Seed skills: hermes skills opt-in --sync",
" Add MCP servers: hermes mcp add", " Tune agent settings: hermes setup agent", None)
_print_setup_summary(config, hermes_home)
@@ -396,8 +379,8 @@ 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, _print_setup_summary, _prompt_api_key, color, print_header, print_info,
print_success, print_warning, prompt_checklist, save_config, save_env_value,
Colors, color, _info, print_header, print_info, _print_setup_summary, print_success, print_warning,
_prompt_api_key, prompt_checklist, save_config, save_env_value,
)
from hermes_cli.config import (get_missing_env_vars, get_missing_config_fields, check_config_version)
print()
@@ -411,9 +394,8 @@ def _run_quick_setup(config: dict, hermes_home):
if not (missing_required or missing_optional or missing_config or current_ver < latest_ver):
print_success("Everything is configured! Nothing to do.")
print()
print_info("Run 'hermes setup' and choose 'Full Setup' to reconfigure,")
print_info("or pick a specific section from the menu.")
_info(None, "Run 'hermes setup' and choose 'Full Setup' to reconfigure,",
"or pick a specific section from the menu.")
return
# Handle missing required env vars

View File

@@ -255,8 +255,8 @@ def _print_section_header(title):
def _print_setup_summary(config: dict, hermes_home):
"""Print the setup completion summary."""
from hermes_cli.setup import (
print_header, print_info, print_warning, get_config_path, get_env_path,
get_nous_subscription_features, Colors, color,
Colors, color, get_config_path, get_env_path, get_nous_subscription_features, _info, print_header,
print_info, 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
@@ -272,9 +272,9 @@ def _print_setup_summary(config: dict, hermes_home):
if not _provider_ready:
print()
print_warning("No inference provider is configured — Hermes cannot chat yet.")
print_info(" Finish this one step with either of:")
print_info(" hermes model (pick any provider/model)")
print_info(" hermes setup --portal (Nous Portal OAuth, no API key)")
_info(" Finish this one step with either of:",
" hermes model (pick any provider/model)",
" hermes setup --portal (Nous Portal OAuth, no API key)")
print()
print_header("Tool Availability Summary")

View File

@@ -18,13 +18,11 @@ _SANDBOX_IMAGE = "nikolaik/python-nodejs:python3.11-nodejs20"
def _prompt_vercel_sandbox_settings(config: dict):
"""Prompt for Vercel Sandbox settings without exposing unsupported disk sizing."""
from hermes_cli.setup import (
get_env_value, print_info, print_warning, prompt, remove_env_value, save_env_value,
get_env_value, _info, print_info, print_warning, prompt, remove_env_value, save_env_value,
)
terminal = config.setdefault("terminal", {})
print()
print_info("Vercel Sandbox settings:")
print_info(" Filesystem persistence uses Vercel snapshots.")
print_info(" Snapshots restore files only; live processes do not continue after sandbox recreation.")
_info(None, "Vercel Sandbox settings:", " Filesystem persistence uses Vercel snapshots.",
" Snapshots restore files only; live processes do not continue after sandbox recreation.")
from tools.terminal_tool import _SUPPORTED_VERCEL_RUNTIMES
@@ -57,9 +55,7 @@ def _prompt_vercel_sandbox_settings(config: dict):
)
terminal["container_disk"] = 51200
print()
print_info("Vercel authentication:")
print_info(" Use a long-lived Vercel access token plus project/team IDs.")
_info(None, "Vercel authentication:", " Use a long-lived Vercel access token plus project/team IDs.")
linked = _read_nearest_vercel_project()
if linked:
print_info(" Found defaults in nearest .vercel/project.json.")
@@ -136,7 +132,7 @@ def _setup_backend_local(config: dict) -> None:
def _setup_backend_docker(config: dict) -> None:
from hermes_cli.setup import print_info, print_success, print_warning, prompt_yes_no
from hermes_cli.setup import _info, print_info, print_success, print_warning, prompt_yes_no
print_success("Terminal backend: Docker")
docker_bin = shutil.which("docker")
if not docker_bin:
@@ -147,13 +143,10 @@ def _setup_backend_docker(config: dict) -> None:
# Image and resource limits use defaults; tune via `hermes setup terminal`.
config["terminal"].setdefault("docker_image", _SANDBOX_IMAGE)
print()
print_info("Docker sandboxes can be protected with the egress credential firewall.")
print_info(
"It routes sandbox traffic through iron-proxy so containers receive "
"proxy tokens instead of real API keys."
)
print_info(" Docker only for now; Modal, SSH, Daytona, and Singularity are not wired yet.")
_info(None, "Docker sandboxes can be protected with the egress credential firewall.",
"It routes sandbox traffic through iron-proxy so containers receive "
"proxy tokens instead of real API keys.",
" Docker only for now; Modal, SSH, Daytona, and Singularity are not wired yet.")
if prompt_yes_no(" Enable egress firewall for Docker sandboxes?", False):
proxy_cfg = config.setdefault("proxy", {})
proxy_cfg["enabled"] = True
@@ -179,7 +172,7 @@ def _setup_backend_singularity(config: dict) -> None:
def _setup_backend_modal(config: dict) -> None:
from hermes_cli.setup import (
cfg_get, get_env_value, get_nous_subscription_features, managed_nous_tools_enabled,
cfg_get, get_env_value, get_nous_subscription_features, _info, managed_nous_tools_enabled,
print_info, print_success, prompt_choice, prompt_yes_no,
)
print_success("Terminal backend: Modal")
@@ -217,9 +210,7 @@ def _setup_backend_modal(config: dict) -> None:
config["terminal"]["modal_mode"] = "direct"
print_info("Requires a Modal account: https://modal.com")
_ensure_sdk("modal", "uv pip install modal")
print()
print_info("Modal authentication:")
print_info(" Get your token at: https://modal.com/settings")
_info(None, "Modal authentication:", " Get your token at: https://modal.com/settings")
if get_env_value("MODAL_TOKEN_ID"):
print_info(" Modal token: already configured")
if not prompt_yes_no(" Update Modal credentials?", False):
@@ -229,11 +220,11 @@ def _setup_backend_modal(config: dict) -> None:
def _setup_backend_daytona(config: dict) -> None:
from hermes_cli.setup import get_env_value, print_info, print_success, prompt_yes_no
from hermes_cli.setup import get_env_value, _info, print_info, print_success, prompt_yes_no
print_success("Terminal backend: Daytona")
print_info("Persistent cloud development environments.")
print_info("Each session gets a dedicated sandbox with filesystem persistence.")
print_info("Sign up at: https://daytona.io")
_info("Persistent cloud development environments.",
"Each session gets a dedicated sandbox with filesystem persistence.",
"Sign up at: https://daytona.io")
_ensure_sdk("daytona", "uv pip install daytona", show_stderr=True)
print()
if get_env_value("DAYTONA_API_KEY"):

View File

@@ -46,7 +46,7 @@ _ESPEAK_INSTALL = {
def _install_neutts_deps() -> bool:
"""Install NeuTTS dependencies with user approval. Returns True on success."""
from hermes_cli.setup import print_info, print_success, print_warning, prompt_yes_no
from hermes_cli.setup import _info, print_info, print_success, print_warning, prompt_yes_no
if not _check_espeak_ng():
hint, install_cmd = _ESPEAK_INSTALL.get(sys.platform, _ESPEAK_INSTALL["linux"])
print()
@@ -64,23 +64,19 @@ def _install_neutts_deps() -> bool:
else:
print_warning("espeak-ng is required for NeuTTS. Install it manually before using NeuTTS.")
print()
print_info("Installing neutts Python package...")
print_info("This will also download the TTS model (~300MB) on first use.")
print()
_info(None, "Installing neutts Python package...",
"This will also download the TTS model (~300MB) on first use.", None)
return _pip_install_tts_package("neutts", ["-U", "neutts[all]", "--quiet"], "uv pip install -U 'neutts[all]'")
def _install_kittentts_deps() -> bool:
"""Install KittenTTS dependencies with user approval. Returns True on success."""
from hermes_cli.setup import print_info
from hermes_cli.setup import _info
wheel_url = (
"https://github.com/KittenML/KittenTTS/releases/download/"
"0.8.1/kittentts-0.8.1-py3-none-any.whl"
)
print()
print_info("Installing kittentts Python package (~25-80MB model downloaded on first use)...")
print()
_info(None, "Installing kittentts Python package (~25-80MB model downloaded on first use)...", None)
return _pip_install_tts_package(
"kittentts", ["-U", wheel_url, "soundfile", "--quiet"], f"uv pip install -U '{wheel_url}' soundfile",
)