diff --git a/honcho_integration/cli.py b/honcho_integration/cli.py index 66ae56eb8..f646f4494 100644 --- a/honcho_integration/cli.py +++ b/honcho_integration/cli.py @@ -190,42 +190,6 @@ def cmd_sync(args) -> None: print() -def cmd_sync(args) -> None: - """Sync Honcho config to all existing profiles. - - Scans all Hermes profiles and creates host blocks for any that don't - have one yet. Inherits settings from the default host block. - Also called automatically during `hermes update`. - """ - try: - from hermes_cli.profiles import list_profiles - profiles = list_profiles() - except Exception as e: - print(f" Could not list profiles: {e}\n") - return - - cfg = _read_config() - if not cfg: - return - - default_block = cfg.get("hosts", {}).get(HOST, {}) - has_key = bool(cfg.get("apiKey") or os.environ.get("HONCHO_API_KEY")) - - if not default_block and not has_key: - return - - created = 0 - for p in profiles: - if p.name == "default": - continue - if clone_honcho_for_profile(p.name): - print(f" Honcho: + {p.name} -> hermes.{p.name}") - created += 1 - - if created: - print(f" Honcho: {created} profile(s) synced.") - - def sync_honcho_profiles_quiet() -> int: """Sync Honcho host blocks for all profiles. Returns count of newly created blocks. @@ -600,12 +564,17 @@ def cmd_status(args) -> None: def _show_peer_cards(hcfg, client) -> None: - """Fetch and display peer cards for the active profile.""" + """Fetch and display peer cards for the active profile. + + Uses get_or_create to ensure the session exists with peers configured. + This is idempotent -- if the session already exists on the server it's + just retrieved, not duplicated. + """ try: from honcho_integration.session import HonchoSessionManager mgr = HonchoSessionManager(honcho=client, config=hcfg) session_key = hcfg.resolve_session_name() - session = mgr.get_or_create(session_key) + mgr.get_or_create(session_key) # User peer card card = mgr.get_peer_card(session_key) diff --git a/honcho_integration/client.py b/honcho_integration/client.py index fdd3fc2e7..6a567b073 100644 --- a/honcho_integration/client.py +++ b/honcho_integration/client.py @@ -166,12 +166,9 @@ class HonchoClientConfig: resolved_host = host or resolve_active_host() api_key = os.environ.get("HONCHO_API_KEY") base_url = os.environ.get("HONCHO_BASE_URL", "").strip() or None - effective_workspace = workspace_id - if effective_workspace == HOST and resolved_host != HOST: - effective_workspace = resolved_host return cls( host=resolved_host, - workspace_id=effective_workspace, + workspace_id=workspace_id, api_key=api_key, environment=os.environ.get("HONCHO_ENVIRONMENT", "production"), base_url=base_url, diff --git a/tests/honcho_integration/test_cli.py b/tests/honcho_integration/test_cli.py index 80ef4ddbc..d3535479e 100644 --- a/tests/honcho_integration/test_cli.py +++ b/tests/honcho_integration/test_cli.py @@ -3,7 +3,7 @@ import json from unittest.mock import patch -from honcho_integration.cli import _resolve_api_key, clone_honcho_for_profile +from honcho_integration.cli import _resolve_api_key, clone_honcho_for_profile, sync_honcho_profiles_quiet class TestResolveApiKey: @@ -115,3 +115,58 @@ class TestCloneHonchoForProfile: assert cfg["hosts"]["hermes.coder"]["aiPeer"] == "hermes.coder" assert cfg["hosts"]["hermes.coder"]["workspace"] == "hermes" # shared + +class TestSyncHonchoProfilesQuiet: + def test_syncs_missing_profiles(self, tmp_path): + config_file = tmp_path / "config.json" + config_file.write_text(json.dumps({ + "apiKey": "key", + "hosts": {"hermes": {"peerName": "alice", "memoryMode": "honcho"}}, + })) + + class FakeProfile: + def __init__(self, name): + self.name = name + self.is_default = name == "default" + + profiles = [FakeProfile("default"), FakeProfile("coder"), FakeProfile("dreamer")] + + with patch("honcho_integration.cli._config_path", return_value=config_file), \ + patch("hermes_cli.profiles.list_profiles", return_value=profiles): + count = sync_honcho_profiles_quiet() + + assert count == 2 + cfg = json.loads(config_file.read_text()) + assert "hermes.coder" in cfg["hosts"] + assert "hermes.dreamer" in cfg["hosts"] + + def test_returns_zero_when_no_honcho(self, tmp_path): + config_file = tmp_path / "config.json" + config_file.write_text("{}") + + with patch("honcho_integration.cli._config_path", return_value=config_file): + count = sync_honcho_profiles_quiet() + + assert count == 0 + + def test_skips_already_synced(self, tmp_path): + config_file = tmp_path / "config.json" + config_file.write_text(json.dumps({ + "apiKey": "key", + "hosts": { + "hermes": {"peerName": "alice"}, + "hermes.coder": {"peerName": "existing"}, + }, + })) + + class FakeProfile: + def __init__(self, name): + self.name = name + self.is_default = name == "default" + + with patch("honcho_integration.cli._config_path", return_value=config_file), \ + patch("hermes_cli.profiles.list_profiles", return_value=[FakeProfile("default"), FakeProfile("coder")]): + count = sync_honcho_profiles_quiet() + + assert count == 0 + diff --git a/tests/honcho_integration/test_client.py b/tests/honcho_integration/test_client.py index ef9a3ad02..655e786c4 100644 --- a/tests/honcho_integration/test_client.py +++ b/tests/honcho_integration/test_client.py @@ -403,7 +403,6 @@ class TestResolveActiveHost: assert resolve_active_host() == "hermes" def test_profiles_import_failure_falls_back(self): - import importlib import sys with patch.dict(os.environ, {}, clear=False): os.environ.pop("HERMES_HONCHO_HOST", None) @@ -424,7 +423,7 @@ class TestProfileScopedConfig: with patch.dict(os.environ, {"HONCHO_API_KEY": "key"}): config = HonchoClientConfig.from_env(host="hermes.coder") assert config.host == "hermes.coder" - assert config.workspace_id == "hermes.coder" + assert config.workspace_id == "hermes" # shared workspace assert config.ai_peer == "hermes.coder" def test_from_env_default_workspace_preserved_for_default_host(self):