mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-06 02:37:05 +08:00
Compare commits
2 Commits
dependabot
...
hermes/her
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
effb44e4bd | ||
|
|
2e4ccbc806 |
@@ -2278,7 +2278,7 @@ For more help on a command:
|
|||||||
skills_inspect.add_argument("identifier", help="Skill identifier")
|
skills_inspect.add_argument("identifier", help="Skill identifier")
|
||||||
|
|
||||||
skills_list = skills_subparsers.add_parser("list", help="List installed skills")
|
skills_list = skills_subparsers.add_parser("list", help="List installed skills")
|
||||||
skills_list.add_argument("--source", default="all", choices=["all", "hub", "builtin"])
|
skills_list.add_argument("--source", default="all", choices=["all", "hub", "builtin", "local"])
|
||||||
|
|
||||||
skills_audit = skills_subparsers.add_parser("audit", help="Re-scan installed hub skills")
|
skills_audit = skills_subparsers.add_parser("audit", help="Re-scan installed hub skills")
|
||||||
skills_audit.add_argument("name", nargs="?", help="Specific skill to audit (default: all)")
|
skills_audit.add_argument("name", nargs="?", help="Specific skill to audit (default: all)")
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ Modular wizard with independently-runnable sections:
|
|||||||
Config files are stored in ~/.hermes/ for easy access.
|
Config files are stored in ~/.hermes/ for easy access.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -1771,6 +1773,100 @@ def setup_tools(config: dict, first_install: bool = False):
|
|||||||
tools_command(first_install=first_install, config=config)
|
tools_command(first_install=first_install, config=config)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# OpenClaw Migration
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
_OPENCLAW_SCRIPT = (
|
||||||
|
PROJECT_ROOT / "optional-skills" / "migration"
|
||||||
|
/ "openclaw-migration" / "scripts" / "openclaw_to_hermes.py"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _offer_openclaw_migration(hermes_home: Path) -> bool:
|
||||||
|
"""Detect ~/.openclaw and offer to migrate during first-time setup.
|
||||||
|
|
||||||
|
Returns True if migration ran successfully, False otherwise.
|
||||||
|
"""
|
||||||
|
openclaw_dir = Path.home() / ".openclaw"
|
||||||
|
if not openclaw_dir.is_dir():
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not _OPENCLAW_SCRIPT.exists():
|
||||||
|
return False
|
||||||
|
|
||||||
|
print()
|
||||||
|
print_header("OpenClaw Installation Detected")
|
||||||
|
print_info(f"Found OpenClaw data at {openclaw_dir}")
|
||||||
|
print_info("Hermes can import your settings, memories, skills, and API keys.")
|
||||||
|
print()
|
||||||
|
|
||||||
|
if not prompt_yes_no("Would you like to import from OpenClaw?", default=True):
|
||||||
|
print_info("Skipping migration. You can run it later via the openclaw-migration skill.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Ensure config.yaml exists before migration tries to read it
|
||||||
|
config_path = get_config_path()
|
||||||
|
if not config_path.exists():
|
||||||
|
save_config(load_config())
|
||||||
|
|
||||||
|
# Dynamically load the migration script
|
||||||
|
try:
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"openclaw_to_hermes", _OPENCLAW_SCRIPT
|
||||||
|
)
|
||||||
|
if spec is None or spec.loader is None:
|
||||||
|
print_warning("Could not load migration script.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
|
||||||
|
# Run migration with the "full" preset, execute mode, no overwrite
|
||||||
|
selected = mod.resolve_selected_options(None, None, preset="full")
|
||||||
|
migrator = mod.Migrator(
|
||||||
|
source_root=openclaw_dir.resolve(),
|
||||||
|
target_root=hermes_home.resolve(),
|
||||||
|
execute=True,
|
||||||
|
workspace_target=None,
|
||||||
|
overwrite=False,
|
||||||
|
migrate_secrets=True,
|
||||||
|
output_dir=None,
|
||||||
|
selected_options=selected,
|
||||||
|
preset_name="full",
|
||||||
|
)
|
||||||
|
report = migrator.migrate()
|
||||||
|
except Exception as e:
|
||||||
|
print_warning(f"Migration failed: {e}")
|
||||||
|
logger.debug("OpenClaw migration error", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Print summary
|
||||||
|
summary = report.get("summary", {})
|
||||||
|
migrated = summary.get("migrated", 0)
|
||||||
|
skipped = summary.get("skipped", 0)
|
||||||
|
conflicts = summary.get("conflict", 0)
|
||||||
|
errors = summary.get("error", 0)
|
||||||
|
|
||||||
|
print()
|
||||||
|
if migrated:
|
||||||
|
print_success(f"Imported {migrated} item(s) from OpenClaw.")
|
||||||
|
if conflicts:
|
||||||
|
print_info(f"Skipped {conflicts} item(s) that already exist in Hermes.")
|
||||||
|
if skipped:
|
||||||
|
print_info(f"Skipped {skipped} item(s) (not found or unchanged).")
|
||||||
|
if errors:
|
||||||
|
print_warning(f"{errors} item(s) had errors — check the migration report.")
|
||||||
|
|
||||||
|
output_dir = report.get("output_dir")
|
||||||
|
if output_dir:
|
||||||
|
print_info(f"Full report saved to: {output_dir}")
|
||||||
|
|
||||||
|
print_success("Migration complete! Continuing with setup...")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Main Wizard Orchestrator
|
# Main Wizard Orchestrator
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -1899,6 +1995,11 @@ def run_setup_wizard(args):
|
|||||||
print()
|
print()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Offer OpenClaw migration before configuration begins
|
||||||
|
if _offer_openclaw_migration(hermes_home):
|
||||||
|
# Reload config in case migration wrote to it
|
||||||
|
config = load_config()
|
||||||
|
|
||||||
# ── Full Setup — run all sections ──
|
# ── Full Setup — run all sections ──
|
||||||
print_header("Configuration Location")
|
print_header("Configuration Location")
|
||||||
print_info(f"Config file: {get_config_path()}")
|
print_info(f"Config file: {get_config_path()}")
|
||||||
|
|||||||
@@ -409,6 +409,7 @@ def do_inspect(identifier: str, console: Optional[Console] = None) -> None:
|
|||||||
def do_list(source_filter: str = "all", console: Optional[Console] = None) -> None:
|
def do_list(source_filter: str = "all", console: Optional[Console] = None) -> None:
|
||||||
"""List installed skills, distinguishing builtins from hub-installed."""
|
"""List installed skills, distinguishing builtins from hub-installed."""
|
||||||
from tools.skills_hub import HubLockFile, ensure_hub_dirs
|
from tools.skills_hub import HubLockFile, ensure_hub_dirs
|
||||||
|
from tools.skills_sync import _read_manifest
|
||||||
from tools.skills_tool import _find_all_skills
|
from tools.skills_tool import _find_all_skills
|
||||||
|
|
||||||
c = console or _console
|
c = console or _console
|
||||||
@@ -416,6 +417,9 @@ def do_list(source_filter: str = "all", console: Optional[Console] = None) -> No
|
|||||||
lock = HubLockFile()
|
lock = HubLockFile()
|
||||||
hub_installed = {e["name"]: e for e in lock.list_installed()}
|
hub_installed = {e["name"]: e for e in lock.list_installed()}
|
||||||
|
|
||||||
|
# Read bundled manifest to distinguish true builtins from local skills
|
||||||
|
bundled_skills = _read_manifest()
|
||||||
|
|
||||||
all_skills = _find_all_skills()
|
all_skills = _find_all_skills()
|
||||||
|
|
||||||
table = Table(title="Installed Skills")
|
table = Table(title="Installed Skills")
|
||||||
@@ -432,22 +436,29 @@ def do_list(source_filter: str = "all", console: Optional[Console] = None) -> No
|
|||||||
if hub_entry:
|
if hub_entry:
|
||||||
source_display = hub_entry.get("source", "hub")
|
source_display = hub_entry.get("source", "hub")
|
||||||
trust = hub_entry.get("trust_level", "community")
|
trust = hub_entry.get("trust_level", "community")
|
||||||
else:
|
elif name in bundled_skills:
|
||||||
source_display = "builtin"
|
source_display = "builtin"
|
||||||
trust = "builtin"
|
trust = "builtin"
|
||||||
|
else:
|
||||||
|
# User-provided local skills (e.g., from ~/.hermes/skills/)
|
||||||
|
source_display = "local"
|
||||||
|
trust = "local"
|
||||||
|
|
||||||
if source_filter == "hub" and not hub_entry:
|
if source_filter == "hub" and not hub_entry:
|
||||||
continue
|
continue
|
||||||
if source_filter == "builtin" and hub_entry:
|
if source_filter == "builtin" and source_display != "builtin":
|
||||||
|
continue
|
||||||
|
if source_filter == "local" and source_display != "local":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
trust_style = {"builtin": "bright_cyan", "trusted": "green", "community": "yellow"}.get(trust, "dim")
|
trust_style = {"builtin": "bright_cyan", "trusted": "green", "community": "yellow", "local": "dim"}.get(trust, "dim")
|
||||||
trust_label = "official" if source_display == "official" else trust
|
trust_label = "official" if source_display == "official" else trust
|
||||||
table.add_row(name, category, source_display, f"[{trust_style}]{trust_label}[/]")
|
table.add_row(name, category, source_display, f"[{trust_style}]{trust_label}[/]")
|
||||||
|
|
||||||
c.print(table)
|
c.print(table)
|
||||||
c.print(f"[dim]{len(hub_installed)} hub-installed, "
|
c.print(f"[dim]{len(hub_installed)} hub-installed, "
|
||||||
f"{len(all_skills) - len(hub_installed)} builtin[/]\n")
|
f"{len([s for s in all_skills if s['name'] in bundled_skills])} builtin, "
|
||||||
|
f"{len([s for s in all_skills if s['name'] not in hub_installed and s['name'] not in bundled_skills])} local[/]\n")
|
||||||
|
|
||||||
|
|
||||||
def do_audit(name: Optional[str] = None, console: Optional[Console] = None) -> None:
|
def do_audit(name: Optional[str] = None, console: Optional[Console] = None) -> None:
|
||||||
@@ -1014,7 +1025,7 @@ def _print_skills_help(console: Console) -> None:
|
|||||||
" [cyan]search[/] <query> Search registries for skills\n"
|
" [cyan]search[/] <query> Search registries for skills\n"
|
||||||
" [cyan]install[/] <identifier> Install a skill (with security scan)\n"
|
" [cyan]install[/] <identifier> Install a skill (with security scan)\n"
|
||||||
" [cyan]inspect[/] <identifier> Preview a skill without installing\n"
|
" [cyan]inspect[/] <identifier> Preview a skill without installing\n"
|
||||||
" [cyan]list[/] [--source hub|builtin] List installed skills\n"
|
" [cyan]list[/] [--source hub|builtin|local] List installed skills\n"
|
||||||
" [cyan]audit[/] [name] Re-scan hub skills for security\n"
|
" [cyan]audit[/] [name] Re-scan hub skills for security\n"
|
||||||
" [cyan]uninstall[/] <name> Remove a hub-installed skill\n"
|
" [cyan]uninstall[/] <name> Remove a hub-installed skill\n"
|
||||||
" [cyan]publish[/] <path> --repo <r> Publish a skill to GitHub via PR\n"
|
" [cyan]publish[/] <path> --repo <r> Publish a skill to GitHub via PR\n"
|
||||||
|
|||||||
257
tests/hermes_cli/test_setup_openclaw_migration.py
Normal file
257
tests/hermes_cli/test_setup_openclaw_migration.py
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
"""Tests for OpenClaw migration integration in the setup wizard."""
|
||||||
|
|
||||||
|
from argparse import Namespace
|
||||||
|
from pathlib import Path
|
||||||
|
from types import ModuleType
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from hermes_cli import setup as setup_mod
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# _offer_openclaw_migration — unit tests
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
class TestOfferOpenclawMigration:
|
||||||
|
"""Test the _offer_openclaw_migration helper in isolation."""
|
||||||
|
|
||||||
|
def test_skips_when_no_openclaw_dir(self, tmp_path):
|
||||||
|
"""Should return False immediately when ~/.openclaw does not exist."""
|
||||||
|
with patch("hermes_cli.setup.Path.home", return_value=tmp_path):
|
||||||
|
assert setup_mod._offer_openclaw_migration(tmp_path / ".hermes") is False
|
||||||
|
|
||||||
|
def test_skips_when_migration_script_missing(self, tmp_path):
|
||||||
|
"""Should return False when the migration script file is absent."""
|
||||||
|
openclaw_dir = tmp_path / ".openclaw"
|
||||||
|
openclaw_dir.mkdir()
|
||||||
|
with (
|
||||||
|
patch("hermes_cli.setup.Path.home", return_value=tmp_path),
|
||||||
|
patch.object(setup_mod, "_OPENCLAW_SCRIPT", tmp_path / "nonexistent.py"),
|
||||||
|
):
|
||||||
|
assert setup_mod._offer_openclaw_migration(tmp_path / ".hermes") is False
|
||||||
|
|
||||||
|
def test_skips_when_user_declines(self, tmp_path):
|
||||||
|
"""Should return False when user declines the migration prompt."""
|
||||||
|
openclaw_dir = tmp_path / ".openclaw"
|
||||||
|
openclaw_dir.mkdir()
|
||||||
|
script = tmp_path / "openclaw_to_hermes.py"
|
||||||
|
script.write_text("# placeholder")
|
||||||
|
with (
|
||||||
|
patch("hermes_cli.setup.Path.home", return_value=tmp_path),
|
||||||
|
patch.object(setup_mod, "_OPENCLAW_SCRIPT", script),
|
||||||
|
patch.object(setup_mod, "prompt_yes_no", return_value=False),
|
||||||
|
):
|
||||||
|
assert setup_mod._offer_openclaw_migration(tmp_path / ".hermes") is False
|
||||||
|
|
||||||
|
def test_runs_migration_when_user_accepts(self, tmp_path):
|
||||||
|
"""Should dynamically load the script and run the Migrator."""
|
||||||
|
openclaw_dir = tmp_path / ".openclaw"
|
||||||
|
openclaw_dir.mkdir()
|
||||||
|
|
||||||
|
# Create a fake hermes home with config
|
||||||
|
hermes_home = tmp_path / ".hermes"
|
||||||
|
hermes_home.mkdir()
|
||||||
|
config_path = hermes_home / "config.yaml"
|
||||||
|
config_path.write_text("agent:\n max_turns: 90\n")
|
||||||
|
|
||||||
|
# Build a fake migration module
|
||||||
|
fake_mod = ModuleType("openclaw_to_hermes")
|
||||||
|
fake_mod.resolve_selected_options = MagicMock(return_value={"soul", "memory"})
|
||||||
|
fake_migrator = MagicMock()
|
||||||
|
fake_migrator.migrate.return_value = {
|
||||||
|
"summary": {"migrated": 3, "skipped": 1, "conflict": 0, "error": 0},
|
||||||
|
"output_dir": str(hermes_home / "migration"),
|
||||||
|
}
|
||||||
|
fake_mod.Migrator = MagicMock(return_value=fake_migrator)
|
||||||
|
|
||||||
|
script = tmp_path / "openclaw_to_hermes.py"
|
||||||
|
script.write_text("# placeholder")
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("hermes_cli.setup.Path.home", return_value=tmp_path),
|
||||||
|
patch.object(setup_mod, "_OPENCLAW_SCRIPT", script),
|
||||||
|
patch.object(setup_mod, "prompt_yes_no", return_value=True),
|
||||||
|
patch.object(setup_mod, "get_config_path", return_value=config_path),
|
||||||
|
patch("importlib.util.spec_from_file_location") as mock_spec_fn,
|
||||||
|
):
|
||||||
|
# Wire up the fake module loading
|
||||||
|
mock_spec = MagicMock()
|
||||||
|
mock_spec.loader = MagicMock()
|
||||||
|
mock_spec_fn.return_value = mock_spec
|
||||||
|
|
||||||
|
def exec_module(mod):
|
||||||
|
mod.resolve_selected_options = fake_mod.resolve_selected_options
|
||||||
|
mod.Migrator = fake_mod.Migrator
|
||||||
|
|
||||||
|
mock_spec.loader.exec_module = exec_module
|
||||||
|
|
||||||
|
result = setup_mod._offer_openclaw_migration(hermes_home)
|
||||||
|
|
||||||
|
assert result is True
|
||||||
|
fake_mod.resolve_selected_options.assert_called_once_with(
|
||||||
|
None, None, preset="full"
|
||||||
|
)
|
||||||
|
fake_mod.Migrator.assert_called_once()
|
||||||
|
call_kwargs = fake_mod.Migrator.call_args[1]
|
||||||
|
assert call_kwargs["execute"] is True
|
||||||
|
assert call_kwargs["overwrite"] is False
|
||||||
|
assert call_kwargs["migrate_secrets"] is True
|
||||||
|
assert call_kwargs["preset_name"] == "full"
|
||||||
|
fake_migrator.migrate.assert_called_once()
|
||||||
|
|
||||||
|
def test_handles_migration_error_gracefully(self, tmp_path):
|
||||||
|
"""Should catch exceptions and return False."""
|
||||||
|
openclaw_dir = tmp_path / ".openclaw"
|
||||||
|
openclaw_dir.mkdir()
|
||||||
|
hermes_home = tmp_path / ".hermes"
|
||||||
|
hermes_home.mkdir()
|
||||||
|
config_path = hermes_home / "config.yaml"
|
||||||
|
config_path.write_text("")
|
||||||
|
|
||||||
|
script = tmp_path / "openclaw_to_hermes.py"
|
||||||
|
script.write_text("# placeholder")
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("hermes_cli.setup.Path.home", return_value=tmp_path),
|
||||||
|
patch.object(setup_mod, "_OPENCLAW_SCRIPT", script),
|
||||||
|
patch.object(setup_mod, "prompt_yes_no", return_value=True),
|
||||||
|
patch.object(setup_mod, "get_config_path", return_value=config_path),
|
||||||
|
patch(
|
||||||
|
"importlib.util.spec_from_file_location",
|
||||||
|
side_effect=RuntimeError("boom"),
|
||||||
|
),
|
||||||
|
):
|
||||||
|
result = setup_mod._offer_openclaw_migration(hermes_home)
|
||||||
|
|
||||||
|
assert result is False
|
||||||
|
|
||||||
|
def test_creates_config_if_missing(self, tmp_path):
|
||||||
|
"""Should bootstrap config.yaml before running migration."""
|
||||||
|
openclaw_dir = tmp_path / ".openclaw"
|
||||||
|
openclaw_dir.mkdir()
|
||||||
|
hermes_home = tmp_path / ".hermes"
|
||||||
|
hermes_home.mkdir()
|
||||||
|
config_path = hermes_home / "config.yaml"
|
||||||
|
# config does NOT exist yet
|
||||||
|
|
||||||
|
script = tmp_path / "openclaw_to_hermes.py"
|
||||||
|
script.write_text("# placeholder")
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("hermes_cli.setup.Path.home", return_value=tmp_path),
|
||||||
|
patch.object(setup_mod, "_OPENCLAW_SCRIPT", script),
|
||||||
|
patch.object(setup_mod, "prompt_yes_no", return_value=True),
|
||||||
|
patch.object(setup_mod, "get_config_path", return_value=config_path),
|
||||||
|
patch.object(setup_mod, "load_config", return_value={"agent": {}}) as mock_load,
|
||||||
|
patch.object(setup_mod, "save_config") as mock_save,
|
||||||
|
patch(
|
||||||
|
"importlib.util.spec_from_file_location",
|
||||||
|
side_effect=RuntimeError("stop early"),
|
||||||
|
),
|
||||||
|
):
|
||||||
|
setup_mod._offer_openclaw_migration(hermes_home)
|
||||||
|
|
||||||
|
# save_config should have been called to bootstrap the file
|
||||||
|
mock_save.assert_called_once_with({"agent": {}})
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Integration with run_setup_wizard — first-time flow
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def _first_time_args() -> Namespace:
|
||||||
|
return Namespace(
|
||||||
|
section=None,
|
||||||
|
non_interactive=False,
|
||||||
|
reset=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSetupWizardOpenclawIntegration:
|
||||||
|
"""Verify _offer_openclaw_migration is called during first-time setup."""
|
||||||
|
|
||||||
|
def test_migration_offered_during_first_time_setup(self, tmp_path):
|
||||||
|
"""On first-time setup, _offer_openclaw_migration should be called."""
|
||||||
|
args = _first_time_args()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch.object(setup_mod, "ensure_hermes_home"),
|
||||||
|
patch.object(setup_mod, "load_config", return_value={}),
|
||||||
|
patch.object(setup_mod, "get_hermes_home", return_value=tmp_path),
|
||||||
|
patch.object(setup_mod, "get_env_value", return_value=""),
|
||||||
|
patch("hermes_cli.auth.get_active_provider", return_value=None),
|
||||||
|
# User presses Enter to start
|
||||||
|
patch("builtins.input", return_value=""),
|
||||||
|
# Mock the migration offer
|
||||||
|
patch.object(
|
||||||
|
setup_mod, "_offer_openclaw_migration", return_value=False
|
||||||
|
) as mock_migration,
|
||||||
|
# Mock the actual setup sections so they don't run
|
||||||
|
patch.object(setup_mod, "setup_model_provider"),
|
||||||
|
patch.object(setup_mod, "setup_terminal_backend"),
|
||||||
|
patch.object(setup_mod, "setup_agent_settings"),
|
||||||
|
patch.object(setup_mod, "setup_gateway"),
|
||||||
|
patch.object(setup_mod, "setup_tools"),
|
||||||
|
patch.object(setup_mod, "save_config"),
|
||||||
|
patch.object(setup_mod, "_print_setup_summary"),
|
||||||
|
):
|
||||||
|
setup_mod.run_setup_wizard(args)
|
||||||
|
|
||||||
|
mock_migration.assert_called_once_with(tmp_path)
|
||||||
|
|
||||||
|
def test_migration_reloads_config_on_success(self, tmp_path):
|
||||||
|
"""When migration returns True, config should be reloaded."""
|
||||||
|
args = _first_time_args()
|
||||||
|
call_order = []
|
||||||
|
|
||||||
|
def tracking_load_config():
|
||||||
|
call_order.append("load_config")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch.object(setup_mod, "ensure_hermes_home"),
|
||||||
|
patch.object(setup_mod, "load_config", side_effect=tracking_load_config),
|
||||||
|
patch.object(setup_mod, "get_hermes_home", return_value=tmp_path),
|
||||||
|
patch.object(setup_mod, "get_env_value", return_value=""),
|
||||||
|
patch("hermes_cli.auth.get_active_provider", return_value=None),
|
||||||
|
patch("builtins.input", return_value=""),
|
||||||
|
patch.object(setup_mod, "_offer_openclaw_migration", return_value=True),
|
||||||
|
patch.object(setup_mod, "setup_model_provider"),
|
||||||
|
patch.object(setup_mod, "setup_terminal_backend"),
|
||||||
|
patch.object(setup_mod, "setup_agent_settings"),
|
||||||
|
patch.object(setup_mod, "setup_gateway"),
|
||||||
|
patch.object(setup_mod, "setup_tools"),
|
||||||
|
patch.object(setup_mod, "save_config"),
|
||||||
|
patch.object(setup_mod, "_print_setup_summary"),
|
||||||
|
):
|
||||||
|
setup_mod.run_setup_wizard(args)
|
||||||
|
|
||||||
|
# load_config called twice: once at start, once after migration
|
||||||
|
assert call_order.count("load_config") == 2
|
||||||
|
|
||||||
|
def test_migration_not_offered_for_existing_install(self, tmp_path):
|
||||||
|
"""Returning users should not see the migration prompt."""
|
||||||
|
args = _first_time_args()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch.object(setup_mod, "ensure_hermes_home"),
|
||||||
|
patch.object(setup_mod, "load_config", return_value={}),
|
||||||
|
patch.object(setup_mod, "get_hermes_home", return_value=tmp_path),
|
||||||
|
patch.object(
|
||||||
|
setup_mod, "get_env_value",
|
||||||
|
side_effect=lambda k: "sk-xxx" if k == "OPENROUTER_API_KEY" else "",
|
||||||
|
),
|
||||||
|
patch("hermes_cli.auth.get_active_provider", return_value=None),
|
||||||
|
# Returning user picks "Exit"
|
||||||
|
patch.object(setup_mod, "prompt_choice", return_value=9),
|
||||||
|
patch.object(
|
||||||
|
setup_mod, "_offer_openclaw_migration", return_value=False
|
||||||
|
) as mock_migration,
|
||||||
|
):
|
||||||
|
setup_mod.run_setup_wizard(args)
|
||||||
|
|
||||||
|
mock_migration.assert_not_called()
|
||||||
Reference in New Issue
Block a user