hermes-bsd/hermes_cli
Teknium 3a9bc9d88a
fix(model picker): unify /model and hermes model lists, add disk cache (#33867)
* fix(model picker): unify /model and `hermes model` model lists, add disk cache

The /model slash picker and `hermes model` were drifting apart. /model
read the raw static `OPENROUTER_MODELS` list (31 entries, including 5
that fail at runtime — no tool-call support or absent from live catalog),
while `hermes model` ran the same list through the live OpenRouter
/v1/models tool-support filter and showed 26 valid entries. Same problem
existed for every other authed provider: /model used curated static
lists, `hermes model` used live /v1/models.

Unifies both surfaces on `provider_model_ids()` and adds a generic
disk-cached wrapper so the picker stays snappy.

Changes
- hermes_cli/models.py: new `cached_provider_model_ids()` —
  ~/.hermes/provider_models_cache.json, 1h TTL, per-provider entries
  keyed by credential fingerprint (env vars + OAuth file mtimes).
  Stale-data-beats-no-data on transient failures. Pair with
  `clear_provider_models_cache(provider=None)`.
- hermes_cli/models.py: `provider_model_ids("nous")` now falls back
  to the docs-hosted manifest (not the in-repo snapshot) when the live
  Portal /models call fails — preserves the model_catalog regression
  guarantee while still going through the unified pathway.
- hermes_cli/model_switch.py: `list_authenticated_providers` routes
  sections 1, 2, and 2b through `cached_provider_model_ids(slug)` with
  curated fallback when the live fetcher comes up empty.
- hermes_cli/model_switch.py: `parse_model_flags` extended to a
  4-tuple, parses `--refresh`.
- cli.py / gateway/run.py / tui_gateway/server.py: updated unpacking;
  CLI + gateway wire `--refresh` to `clear_provider_models_cache()`.
- hermes_cli/main.py: `hermes model --refresh` argparse flag.
- hermes_cli/commands.py: `/model` args_hint advertises `--refresh`.
- tests/hermes_cli/test_inventory.py: refresh stale comment.

Live PTY parity verification
- /model → OpenRouter row: `(26 models)` (was 31, with broken entries)
- `hermes model` → OpenRouter: 26 models (unchanged)
- The 5 dropped entries: `pareto-code` (no tool-call support),
  `gemini-3-pro-image-preview` (no tool-call support),
  `elephant-alpha`, `hy3-preview:free`, `ring-2.6-1t:free` (gone
  from OpenRouter's live catalog).

Live PTY timing
- First /model open, empty cache: 4624 ms (full network round trip
  across every authed provider)
- Second /model open, warm cache: 51 ms (90× faster)
- `/model --refresh` clears the disk cache and re-fetches.

Cache schema (~/.hermes/provider_models_cache.json, ~3 KB):
  { "anthropic": {"fp": "<sha256:16>", "at": 1748..., "models": [...]},
    ... }

Targeted tests: tests/hermes_cli/ + gateway model tests + tui_gateway —
5855/5855 pass.

* fix(model picker): use blake2b for cache fingerprint to silence CodeQL

py/weak-sensitive-data-hashing flagged the sha256 call in
_credential_fingerprint() as a high-severity alert because the input
includes env var values whose names contain *_API_KEY / *_TOKEN.

The hash is used solely as a cache-bust identity — never reversed, never
stored, collisions are harmless (worst case: cache miss → live re-fetch).
blake2b serves the same purpose and isn't flagged by this rule.

Functional behavior identical: 16-hex-char digest, cache hit/miss logic
unchanged. Live re-verified — 26 OpenRouter models, warm-cache 78ms.
2026-05-28 11:33:16 -07:00
..
dashboard_auth feat(dashboard-auth): HERMES_DASHBOARD_PUBLIC_URL / dashboard.public_url override 2026-05-27 02:12:27 -07:00
proxy fix(xai-proxy): handle 429 rate-limit responses in proxy retry path 2026-05-28 02:36:37 -07:00
__init__.py chore: release v0.15.0 (2026.5.28) (#34008) 2026-05-28 10:45:33 -07:00
_parser.py Fix CLI verbose tool progress config fallback 2026-05-23 21:03:51 -07:00
_subprocess_compat.py
auth.py fix(xai-oauth): accept bare-code manual paste (state=None) (#26923) (#33880) 2026-05-28 05:47:30 -07:00
auth_commands.py fix(cli): show masked feedback for secret prompts 2026-05-25 01:20:33 -07:00
azure_detect.py
backup.py fix: limit pre-update state snapshots 2026-05-28 02:45:25 -07:00
banner.py fix(docker): bake build-time git SHA into the image 2026-05-28 15:14:05 +10:00
browser_connect.py
build_info.py fix(docker): bake build-time git SHA into the image 2026-05-28 15:14:05 +10:00
bundles.py
callbacks.py fix(cli): show masked feedback for secret prompts 2026-05-25 01:20:33 -07:00
checkpoints.py
claw.py
cli_output.py fix(cli): show masked feedback for secret prompts 2026-05-25 01:20:33 -07:00
clipboard.py
codex_models.py fix(codex): drop dead model slugs that HTTP 400 on ChatGPT Pro (#33424) 2026-05-27 12:16:15 -07:00
codex_runtime_plugin_migration.py
codex_runtime_switch.py
colors.py
commands.py fix(model picker): unify /model and hermes model lists, add disk cache (#33867) 2026-05-28 11:33:16 -07:00
completion.py
config.py fix(gateway): default media-delivery validation to denylist-only, restore .md delivery (#34022) 2026-05-28 11:32:36 -07:00
container_boot.py fix(docker): make s6 lifecycle work for the unprivileged hermes user 2026-05-25 12:23:23 +10:00
copilot_auth.py
cron.py
curator.py
curses_ui.py fix(cli): clamp curses color 8 for 8-color terminals (Docker) 2026-05-21 23:40:58 -07:00
debug.py fix(debug): redact BlueBubbles webhook secrets 2026-05-24 15:43:48 -07:00
default_soul.py
dep_ensure.py
dingtalk_auth.py
doctor.py remove Vercel AI Gateway and Vercel Sandbox (#33067) 2026-05-27 00:43:32 -07:00
dump.py fix(docker): bake build-time git SHA into the image 2026-05-28 15:14:05 +10:00
env_loader.py fix(secrets): only apply external secrets once per HERMES_HOME per process (#32271) 2026-05-25 15:18:55 -07:00
fallback_cmd.py fix(fallback): merge fallback_providers with legacy fallback_model configurations 2026-05-23 05:24:57 -07:00
fallback_config.py fix(fallback): merge fallback_providers with legacy fallback_model configurations 2026-05-23 05:24:57 -07:00
gateway.py feat(docker): auto-redirect gateway run to supervised mode inside s6 image 2026-05-28 12:42:13 +10:00
gateway_windows.py fix(gateway): drain on Windows hermes gateway stop so sessions survive restart (#33798) 2026-05-28 03:25:32 -07:00
goals.py
hooks.py
inventory.py
kanban.py fix(kanban): close kanban.db FD after every connect() in long-lived processes 2026-05-27 22:07:49 -07:00
kanban_db.py fix(kanban): content-addressed corrupt-DB backup filename 2026-05-28 03:38:09 -07:00
kanban_decompose.py fix(kanban): close kanban.db FD after every connect() in long-lived processes 2026-05-27 22:07:49 -07:00
kanban_diagnostics.py
kanban_specify.py fix(kanban): close kanban.db FD after every connect() in long-lived processes 2026-05-27 22:07:49 -07:00
kanban_swarm.py
logs.py
main.py fix(model picker): unify /model and hermes model lists, add disk cache (#33867) 2026-05-28 11:33:16 -07:00
mcp_catalog.py feat(mcp): Nous-approved MCP catalog with interactive picker (#30870) 2026-05-26 12:48:14 -07:00
mcp_config.py feat(mcp): Nous-approved MCP catalog with interactive picker (#30870) 2026-05-26 12:48:14 -07:00
mcp_picker.py feat(mcp): Nous-approved MCP catalog with interactive picker (#30870) 2026-05-26 12:48:14 -07:00
memory_setup.py fix(cli): show masked feedback for secret prompts 2026-05-25 01:20:33 -07:00
migrate.py
model_catalog.py
model_normalize.py remove Vercel AI Gateway and Vercel Sandbox (#33067) 2026-05-27 00:43:32 -07:00
model_switch.py fix(model picker): unify /model and hermes model lists, add disk cache (#33867) 2026-05-28 11:33:16 -07:00
models.py fix(model picker): unify /model and hermes model lists, add disk cache (#33867) 2026-05-28 11:33:16 -07:00
nous_account.py feat(auth) normalise the way in which we check whether a user has free/paid access to nous portal so we can expose behaviour and error messages accordingly. 2026-05-28 00:19:31 -07:00
nous_subscription.py fix(auth): refresh Nous entitlement in tool menus 2026-05-28 00:19:31 -07:00
oneshot.py fix(provider): make config.yaml model.provider the single source of truth (#31222) 2026-05-23 18:18:41 -07:00
pairing.py
platforms.py
plugins.py feat(plugins): add register_dashboard_auth_provider hook on PluginContext 2026-05-27 02:12:27 -07:00
plugins_cmd.py feat(context-engine): host contract for external context engines 2026-05-28 01:45:30 -07:00
portal_cli.py feat(portal): one-shot setup, status CLI, and Nous-included markers (#30860) 2026-05-23 02:39:09 -07:00
profile_describer.py fix(skills): prune dependency/venv dirs from all skill scanners (#30042) 2026-05-21 14:18:02 -07:00
profile_distribution.py fix(profile): reject symlinks in distributions (#25292) 2026-05-25 05:07:58 -07:00
profiles.py fix(security): tighten .env file permissions to 0600 at all creation sites 2026-05-25 03:40:47 -07:00
providers.py remove Vercel AI Gateway and Vercel Sandbox (#33067) 2026-05-27 00:43:32 -07:00
psutil_android.py fix(android): reject unsafe tar members in psutil compatibility installer 2026-05-28 02:36:09 -07:00
pt_input_extras.py
pty_bridge.py
relaunch.py
runtime_provider.py
secret_prompt.py fix(cli): show masked feedback for secret prompts 2026-05-25 01:20:33 -07:00
secrets_cli.py fix(cli): show masked feedback for secret prompts 2026-05-25 01:20:33 -07:00
security_advisories.py
security_audit.py feat(security): on-demand supply-chain audit via OSV.dev (#31460) 2026-05-24 15:15:16 -07:00
send_cmd.py
service_manager.py fix(docker): align HOME for dashboard and s6 gateway services (#33481) 2026-05-28 13:42:27 +10:00
session_recap.py
setup.py remove Vercel AI Gateway and Vercel Sandbox (#33067) 2026-05-27 00:43:32 -07:00
skills_config.py
skills_hub.py fix(skills-hub): stop ellipsis-truncating the Identifier column (#33810) 2026-05-28 04:53:13 -07:00
skin_engine.py
slack_cli.py
status.py feat(auth) normalise the way in which we check whether a user has free/paid access to nous portal so we can expose behaviour and error messages accordingly. 2026-05-28 00:19:31 -07:00
stdio.py
timeouts.py
tips.py docs(auth): replace stale 'hermes login' references with 'hermes auth add' 2026-05-26 15:41:11 -07:00
tools_config.py fix: expose context engine tools with saved toolsets 2026-05-28 00:28:42 -07:00
uninstall.py
voice.py
web_server.py feat(dashboard-auth): honour X-Forwarded-Prefix + __Host-/__Secure- cookies 2026-05-27 02:12:27 -07:00
webhook.py fix(state): restrict sensitive store file permissions 2026-05-24 04:55:18 -07:00
xai_retirement.py