mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-21 10:22:18 +00:00
feat(managed-scope): surface managed scope in config show and doctor
- show_config prints an administrator header naming the managed source and lists the pinned config/env keys when a scope is active (silent otherwise). - hermes doctor gains a managed_scope_check under Configuration Files that reports the resolved managed dir + pinned key counts, and flags a HERMES_MANAGED_DIR redirect (the documented foot-gun).
This commit is contained in:
parent
4f9e15df97
commit
ddd519ea70
3 changed files with 128 additions and 2 deletions
|
|
@ -6308,12 +6308,38 @@ def redact_key(key: str) -> str:
|
|||
def show_config():
|
||||
"""Display current configuration."""
|
||||
config = load_config()
|
||||
|
||||
|
||||
print()
|
||||
print(color("┌─────────────────────────────────────────────────────────┐", Colors.CYAN))
|
||||
print(color("│ ⚕ Hermes Configuration │", Colors.CYAN))
|
||||
print(color("└─────────────────────────────────────────────────────────┘", Colors.CYAN))
|
||||
|
||||
|
||||
# Managed scope: surface that some settings are administrator-pinned so the
|
||||
# user understands why their config.yaml value may not be the effective one.
|
||||
from hermes_cli import managed_scope
|
||||
|
||||
_managed_keys = managed_scope.managed_config_keys()
|
||||
_managed_env = managed_scope.load_managed_env()
|
||||
if _managed_keys or _managed_env:
|
||||
_managed_dir = managed_scope.get_managed_dir()
|
||||
print()
|
||||
print(color(
|
||||
f" ⚷ Some settings are managed by your administrator ({_managed_dir}) "
|
||||
f"and cannot be changed",
|
||||
Colors.YELLOW,
|
||||
Colors.BOLD,
|
||||
))
|
||||
if _managed_keys:
|
||||
print(color(
|
||||
f" Managed config keys: {', '.join(sorted(_managed_keys))}",
|
||||
Colors.YELLOW,
|
||||
))
|
||||
if _managed_env:
|
||||
print(color(
|
||||
f" Managed env keys: {', '.join(sorted(_managed_env))}",
|
||||
Colors.YELLOW,
|
||||
))
|
||||
|
||||
# Paths
|
||||
print()
|
||||
print(color("◆ Paths", Colors.CYAN, Colors.BOLD))
|
||||
|
|
|
|||
|
|
@ -462,6 +462,31 @@ def _build_apikey_providers_list() -> list:
|
|||
return _static
|
||||
|
||||
|
||||
def managed_scope_check() -> None:
|
||||
"""Report the active managed scope (resolved dir + pinned key counts).
|
||||
|
||||
Silent when no managed scope is present. When the managed directory was
|
||||
resolved from the HERMES_MANAGED_DIR override (rather than the system
|
||||
default), that is surfaced too — a redirected scope is the documented
|
||||
foot-gun (see docs/design/managed-scope.md §7) and an operator should see it.
|
||||
"""
|
||||
try:
|
||||
from hermes_cli import managed_scope
|
||||
managed_dir = managed_scope.get_managed_dir()
|
||||
except Exception: # noqa: BLE001 — diagnostics must never crash
|
||||
return
|
||||
if managed_dir is None:
|
||||
return
|
||||
n_cfg = len(managed_scope.managed_config_keys())
|
||||
n_env = len(managed_scope.load_managed_env())
|
||||
check_ok(
|
||||
f"Managed scope active: {n_cfg} config key(s), {n_env} env key(s) "
|
||||
f"pinned by {managed_dir}"
|
||||
)
|
||||
if os.environ.get("HERMES_MANAGED_DIR", "").strip():
|
||||
check_info(f"managed dir set via HERMES_MANAGED_DIR={managed_dir}")
|
||||
|
||||
|
||||
def run_doctor(args):
|
||||
"""Run diagnostic checks."""
|
||||
should_fix = getattr(args, 'fix', False)
|
||||
|
|
@ -642,6 +667,8 @@ def run_doctor(args):
|
|||
check_warn(name, "(optional, not installed)")
|
||||
|
||||
_section("Configuration Files")
|
||||
# Managed scope (administrator-pinned config/env), when present.
|
||||
managed_scope_check()
|
||||
# Check ~/.hermes/.env (primary location for user config)
|
||||
env_path = HERMES_HOME / '.env'
|
||||
if env_path.exists():
|
||||
|
|
|
|||
73
tests/hermes_cli/test_managed_scope_surfacing.py
Normal file
73
tests/hermes_cli/test_managed_scope_surfacing.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
"""Surfacing tests — managed scope shown in `config show` and `hermes doctor`."""
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def homes(tmp_path, monkeypatch):
|
||||
home = tmp_path / "home"
|
||||
home.mkdir()
|
||||
managed = tmp_path / "managed"
|
||||
managed.mkdir()
|
||||
monkeypatch.setenv("HERMES_HOME", str(home))
|
||||
monkeypatch.setenv("HERMES_MANAGED_DIR", str(managed))
|
||||
(home / "config.yaml").write_text("model:\n default: user/model\n", encoding="utf-8")
|
||||
(managed / "config.yaml").write_text(
|
||||
"model:\n default: managed/model\n", encoding="utf-8"
|
||||
)
|
||||
import hermes_cli.config as cfg
|
||||
from hermes_cli import managed_scope
|
||||
|
||||
cfg._LOAD_CONFIG_CACHE.clear()
|
||||
cfg._RAW_CONFIG_CACHE.clear()
|
||||
managed_scope.invalidate_managed_cache()
|
||||
return home, managed
|
||||
|
||||
|
||||
def test_config_show_flags_managed(homes, capsys):
|
||||
from hermes_cli.config import show_config
|
||||
|
||||
show_config()
|
||||
out = capsys.readouterr().out.lower()
|
||||
assert "managed" in out # header + key list present
|
||||
assert "model.default" in out # the pinned key is named
|
||||
assert "managed/model" in out # effective (managed) value, not user/model
|
||||
|
||||
|
||||
def test_config_show_no_managed_scope_silent(tmp_path, monkeypatch, capsys):
|
||||
"""With no managed scope, the managed header must not appear."""
|
||||
home = tmp_path / "home"
|
||||
home.mkdir()
|
||||
monkeypatch.setenv("HERMES_HOME", str(home))
|
||||
monkeypatch.setenv("HERMES_MANAGED_DIR", str(tmp_path / "nope"))
|
||||
(home / "config.yaml").write_text("model:\n default: user/model\n", encoding="utf-8")
|
||||
import hermes_cli.config as cfg
|
||||
from hermes_cli import managed_scope
|
||||
|
||||
cfg._LOAD_CONFIG_CACHE.clear()
|
||||
cfg._RAW_CONFIG_CACHE.clear()
|
||||
managed_scope.invalidate_managed_cache()
|
||||
from hermes_cli.config import show_config
|
||||
|
||||
show_config()
|
||||
out = capsys.readouterr().out.lower()
|
||||
assert "managed by your administrator" not in out
|
||||
|
||||
|
||||
def test_doctor_reports_managed_scope(homes, capsys):
|
||||
# homes fixture has 1 managed config key (model.default) and 0 managed env keys.
|
||||
from hermes_cli import doctor
|
||||
|
||||
doctor.managed_scope_check()
|
||||
out = capsys.readouterr().out.lower()
|
||||
assert "managed scope active" in out
|
||||
assert str(homes[1]).lower() in out # resolved dir reported
|
||||
assert "1 config key" in out
|
||||
|
||||
|
||||
def test_doctor_silent_with_no_managed_scope(tmp_path, monkeypatch, capsys):
|
||||
monkeypatch.setenv("HERMES_MANAGED_DIR", str(tmp_path / "nope"))
|
||||
from hermes_cli import managed_scope, doctor
|
||||
|
||||
managed_scope.invalidate_managed_cache()
|
||||
doctor.managed_scope_check()
|
||||
assert capsys.readouterr().out.strip() == ""
|
||||
Loading…
Add table
Add a link
Reference in a new issue