Codex 5h/weekly windows (and Anthropic/OpenRouter limits) were only reachable through the interactive `/usage` slash command, so cron jobs and shell scripts had no way to read quota state (#33094, #57476). `hermes usage` fetches the same snapshot through `agent.account_usage.fetch_account_usage` — the credential resolution a session with no live agent uses — and prints it with the same renderer; `--json` emits one stable, documented document, exit 1 with a single stderr line when no credential is configured or the fetch fails. Slim redo of #81819 (@himanusia): top-level command instead of `hermes auth usage`, no --all/--account/--reset (the per-entry paths rendered the wrong account for anthropic and the default path bypassed the runtime resolver). Co-authored-by: himanusia <himanusia@users.noreply.github.com>
65 lines
2.6 KiB
Python
65 lines
2.6 KiB
Python
"""``hermes usage`` — the non-interactive /usage surface (issue #33094).
|
|
|
|
Drives the real ``hermes`` argparse entrypoint; only the network fetch is replaced with a snapshot
|
|
(``agent.account_usage.fetch_account_usage`` is what ``cmd_usage`` reads at call time).
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from unittest.mock import patch
|
|
|
|
from agent.account_usage import AccountUsageSnapshot, AccountUsageWindow
|
|
from hermes_cli import main as hermes_main
|
|
|
|
_SNAPSHOT = AccountUsageSnapshot(
|
|
provider="openai-codex", source="usage_api", fetched_at=datetime(2026, 9, 19, 12, 0, tzinfo=timezone.utc),
|
|
plan="Plus",
|
|
windows=(
|
|
AccountUsageWindow(label="Session", used_percent=37.0, reset_at=datetime(2026, 9, 19, 21, 0, tzinfo=timezone.utc)),
|
|
AccountUsageWindow(label="Weekly", used_percent=12.5, reset_at=None),
|
|
),
|
|
details=("You have 1 reset banked - use /usage reset to activate",),
|
|
)
|
|
|
|
|
|
def _run(argv, fetch):
|
|
with patch.object(hermes_main, "_plugin_cli_discovery_needed", return_value=False), \
|
|
patch("agent.account_usage.fetch_account_usage", fetch), \
|
|
patch.object(sys, "argv", ["hermes", *argv]):
|
|
try:
|
|
hermes_main.main()
|
|
except SystemExit as exc:
|
|
return int(exc.code or 0)
|
|
return 0
|
|
|
|
|
|
def test_hermes_usage_json_is_one_stable_document(capsys):
|
|
calls = []
|
|
|
|
def fetch(provider, **kwargs):
|
|
calls.append(provider)
|
|
return _SNAPSHOT
|
|
|
|
assert _run(["usage", "--json", "--provider", "openai-codex"], fetch) == 0
|
|
out, err = capsys.readouterr()
|
|
doc = json.loads(out)
|
|
assert calls == ["openai-codex"] and err == ""
|
|
assert doc["provider"] == "openai-codex" and doc["plan"] == "Plus"
|
|
assert doc["fetched_at"] == "2026-09-19T12:00:00+00:00"
|
|
assert doc["windows"] == [
|
|
{"label": "Session", "used_percent": 37.0, "resets_at": "2026-09-19T21:00:00+00:00", "detail": None},
|
|
{"label": "Weekly", "used_percent": 12.5, "resets_at": None, "detail": None},
|
|
]
|
|
assert doc["details"] == ["You have 1 reset banked - use /usage reset to activate"]
|
|
assert set(doc) == {"provider", "source", "title", "plan", "fetched_at", "windows", "details", "unavailable_reason"}
|
|
|
|
|
|
def test_hermes_usage_without_credential_exits_nonzero_with_one_stderr_line(capsys):
|
|
# fetch_account_usage returns None when no credential resolves (or the fetch fails) — script-friendly failure.
|
|
assert _run(["usage", "--json", "--provider", "openai-codex"], lambda provider, **kw: None) == 1
|
|
out, err = capsys.readouterr()
|
|
assert out == ""
|
|
assert err.count("\n") == 1 and "openai-codex" in err
|
|
|