Commit Graph
47 Commits
Author SHA1 Message Date
Teknium e3a962d087 refactor(mcp): Hermes as an MCP server leaves core for the mcp-serve plugin
`hermes mcp serve` (mcp_serve.py) moves to the official mcp-serve plugin in
hermes-official-plugins, which registers `hermes mcp-serve` and also carries
the `--tools` mode from #83364.

- delete mcp_serve.py and tests/test_mcp_serve.py (the suite moved with it)
- drop the serve subparser, its dispatcher branch, the mcp agent-subcommand
  row and the inline MCP discovery path only `mcp serve` reached
- LEFT_CORE row (command + old_command): `hermes mcp serve` and a bare
  `hermes mcp-serve` without the plugin print the new spelling and the
  install line on stderr. MCP clients live outside Hermes, so no home shows
  the feature in use and nothing installs automatically.
- docs, locale tips and the bundled cli-reference point at the plugin

agent/transports/hermes_tools_mcp_server.py stays: the codex_app_server
runtime launches it as its hermes-tools MCP server.
2026-10-21 17:06:03 -07:00
Teknium 607a23e858 Merge origin/main (#30: 54 commits to 1744a19e0d) into feat/unified-gateway-runtime
8 conflicts, all from main's lint sweep (#135281 "Harder linter fixes" + the RUF series:
implicit-Optional, unused-unpacked-variable, next(...) for [...][0]). No other main commit touches
a conflicted file. Resolution: the branch side of each file plus main's exact autofix
(ruff 0.15.10, main's ruff.strict.toml, --fix --unsafe-fixes) on it. ruff.strict.toml takes main's
copy (the newly enabled rules). tests/cron/test_bot_chat_delivery_child_env.py stays deleted
(1cc9cb9199 retired its lane).
2026-10-08 21:38:54 -07:00
Teknium 71704ffb49 refactor: move Spotify out of core into the spotify catalog plugin
The spotify toolset (plugins/spotify) and its loopback-PKCE login (hermes_cli/auth_spotify.py)
now live in NousResearch/hermes-spotify, installed from the plugin catalog. Same pattern as the
Home Assistant move (fb9fc8a3dd): a LEFT_CORE row installs the plugin for every home that was
using it (a Spotify login in auth.json, or the spotify toolset in platform_toolsets) on
`hermes update` / first start; the toolset name, tool names, schemas, env vars and stored login
(providers.spotify) are unchanged, so nobody re-authenticates.

The one user-visible change is the login command: plugins cannot extend `hermes auth`, so the
plugin registers `hermes spotify login|status|logout`. LeftCoreFeature gains a `cli` field and
moved_command_hint() turns `hermes auth [status|logout] spotify`, and `hermes spotify` while the
plugin is not loaded, into a line naming the new command and the install/enable step for this
home. The dashboard's "needs login" hint names the new command too.

Core drops the Spotify auth constants/dispatch, the CONFIGURABLE_TOOLSETS row and post-setup
step, the core `spotify` entries in the shared-metrics toolset enums, its tests, and the docs
(en + zh-Hans) now point at `hermes plugins install spotify` / `hermes spotify login`.
2026-10-08 19:00:45 -07:00
Teknium 3712e99d60 fix(cli): accept hermes --tui --checkpoints at the top level
Follow-up to kshitij review item #1 (k1, 2ee1cd1ebb).

k1 made -s / --checkpoints / --pass-session-id / --accept-hooks creation policy of
the TUI session. Three of them were declared on the top-level parser, but
--checkpoints existed only on the `chat` subparser, so `hermes --tui --checkpoints`
(the TUI form the docs and earlier gateway refusals pointed users to) failed with
"unrecognized arguments: --checkpoints" before anything launched.

Declare it on the top-level parser like --pass-session-id, and make the chat copy
default to SUPPRESS so `hermes --checkpoints chat` keeps the value (the subparser
contract pinned by test_chat_subparser_inherited_value_flags_use_suppress).

The `--verbose` refusal had the same gap: it pointed at `hermes --tui -v`, which
the top-level parser also rejects (-v is a `chat` option). It now names
`hermes chat --tui -v`, and a test parses every `hermes ...` command the
relocation hints name.

Live (tmux, fake HOME, loopback model): `hermes --tui --checkpoints` reaches ready,
the session policy carries checkpoints.enabled=true, and a write_file turn fills the
checkpoint store (0 entries without the flag). `-s probe-skill` puts the skill body
in the model's system prompt, `--pass-session-id` puts "Session ID:" there, and
`--accept-hooks` records the profile hook in shell-hooks-allowlist.json.

Red on r5-picked: test_checkpoints_parses_at_the_top_level_and_survives_chat
([--tui --checkpoints] and [--checkpoints chat]) and
test_every_relocation_hint_names_a_command_that_parses.

Review: https://github.com/NousResearch/hermes-agent/pull/135061
2026-10-08 12:07:01 -07:00
Teknium 1e500ebce2 Merge origin/main (#16: 4,465 commits to 9dcab1e440) into feat/unified-gateway-runtime
99 conflicted files resolved in nine area lanes (Desktop composer, Desktop renderer/shared
client, Electron main, Ink, tui_gateway, gateway, state, CLI/config, cron/tools/CI). The
unified runtime's architecture wins where it deliberately replaced a mechanism (pool,
fallback lanes, claim poller, renderer queue); every orthogonal main change is kept, ported
into the <stem>_<topic> sibling that now owns relocated code.

Notable ports and repairs in this merge:
- tui_gateway/server.py relocated regions: atomic _attach_built_agent (#49852), slash ranking
  registry names, minted-key helpers methods_session.py calls, request_overrides, service tier.
- gateway/run_turn*.py: i18n accessors for the removed reply constants, first-contact
  internal/DM rule, rotated-compression persist, fast-mode tiers, prompt-pin rehydration.
- webhook: route_from_chat_id decodes main's opaque webhook:v2 chat ids (old ids still read).
- ws.py: main's per-connection dispatch queue with authority-first dispatch inside it.
- state: schema event for runtime revision renumbered to 31; worker mirrors updated for
  main's message uid/merge fields, on_contended lease arg, compression columns.
- config: branch's bot_chat_delivery_timeout removal renumbered v47 -> v50.
- Electron: primary-profile pin, redirect header scoping, remote fetchJsonForBackend, SSH
  teardown on profile delete, held-update dial gate restored (lost in earlier merges).
- Desktop: canonical clarify.request mapped to main's questions[] card; replay truncation vs
  snapshot_required; periodic secondary prune tick restored; DELETE already_absent restored.
- cron/tools: diagnostic category on cron failure notices restored; owner_holds_delivery
  ported onto the authority; admitted receipts can no longer be cancelled locally.
2026-10-06 14:24:24 -07:00
teknium1 d450c3659e fix(memory): a missing catalog memory provider names its install command
When a provider that left core is configured but not installed (offline,
lazy installs off, a failed migration), doctor, memory status, memory setup
<name> and the CLI's unknown-command error now print
`hermes [-p <profile>] plugins install <name>` instead of pointing at
hermes memory setup or ~/.hermes/plugins/. In-tree catalog only, no network;
-p is added whenever a bare command would install into another profile.
2026-10-02 04:39:28 -07:00
teknium1 c6dc7ab120 Merge remote-tracking branch 'origin/main' into feat/unified-gateway-runtime
Merge #8: 2,771 commits from main (4b05f764fa). Ports main's moved code into the
unified runtime (run.py/run_turn.py splits, tui_gateway server, backup/restore
split), keeps the detached-daemon Desktop lifecycle, and adapts main's new tests
to the owner/authority contracts (canonical session deletes, retired-id
tombstones, zero-writer browsing, hermes_yaml, platforms() markers).
2026-09-27 02:30:04 -07:00
brooklyn! 93f7617406 feat(tui): add native terminal mode defaults 2026-09-25 17:57:38 -05:00
teknium1 eec5bd6673 Merge remote-tracking branch 'origin/main' into fix/106742-thread-resolution
4,048 upstream commits (Sep 16-23) composed onto the unified gateway runtime by six
per-domain conflict lanes (gateway, desktop/shared, cli/state, cron, bots/tui_gateway, ink).
Retired lanes stay retired (Desktop secondary pool + exit supervisor, deferred Bot Chat
pending queue, subprocess retry adoption); main's gateway.standalone, host-role claim,
clarify capability gate, config-read caches and idle gates ride on top.
2026-09-23 03:25:31 -07:00
ethernet a6ae6ace51 Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	.github/workflows/js-tests.yml
#	agent/model_metadata.py
#	apps/desktop/electron/main.ts
#	apps/desktop/scripts/bundle-electron-main.mjs
#	apps/desktop/src/app/settings/about-settings.tsx
#	apps/desktop/src/app/settings/gateway-settings.test.tsx
#	apps/desktop/src/app/settings/gateway-settings.tsx
#	apps/desktop/src/app/updates-overlay.tsx
#	gateway/shutdown_flush.py
#	hermes_bootstrap.py
#	hermes_cli/local_runtime/binaries.py
#	hermes_cli/main.py
#	hermes_cli/managed_uv.py
#	hermes_cli/update_cmd.py
#	hermes_cli/update_cmd_deps.py
#	hermes_cli/update_cmd_fleet.py
#	hermes_cli/update_cmd_maint.py
#	hermes_cli/update_receipt.py
#	hermes_cli/update_serve_obligations.py
#	hermes_constants.py
#	tests/hermes_cli/test_doctor.py
#	tests/hermes_cli/test_managed_uv.py
#	tests/hermes_cli/test_pending_supervisor_recovery.py
#	tests/hermes_cli/test_startup_fast_guards.py
#	tests/hermes_cli/test_update_desktop_stale_warning.py
#	tests/hermes_cli/test_update_fleet_restart_pending.py
#	tests/hermes_state/test_hermes_state.py
#	tests/tools/test_tirith_security.py
#	tools/bot_relay.py
#	tools/checkpoint_manager.py
#	tools/write_approval.py
#	website/docs/getting-started/updating.md
#	website/docs/reference/environment-variables.md
2026-09-18 17:26:10 -04:00
teknium1 1d46d0305b fix(cli): --help lists gateway status and the --profile long form; trim epilogue tests
Follow-up to the salvaged #114497 rows: the issue asks for the profile-scoped
gateway lifecycle, and `status` is the verb an operator reaches for right
after start/stop, so it joins the epilogue; the `-p` row now names the
`--profile` long form too. The install row moves next to its siblings so
the gateway verbs read as one block.

Tests are trimmed to two invariants on the RENDERED help (the wiring, not
the constant): the profile-scoped form is documented while `-p` stays a
pre-argparse flag (never a parser option), and every gateway service verb
is listed.
2026-09-18 09:50:52 -07:00
Konstantin Khlopkov bd72c4205d fix(cli): document -p <profile> and the gateway service verbs in --help 2026-09-18 09:50:52 -07:00
ethernet b4a294fff9 Merge origin/main; keep PM as plugin dependency owner
Reconcile plugin declarations and validation through PM's atomic generation publication; preserve external runtimes, target markers, and conflict refusal. Keep one source-update completion owner and port upstream lifecycle changes to the PM desktop/runtime paths.
2026-09-17 13:52:05 -04:00
teknium1 485d5f6848 Merge remote-tracking branch 'origin/main' into fix/106742-thread-resolution
# Conflicts:
#	gateway/run_turn_runner.py
2026-09-16 17:57:23 -07:00
teknium1 2452b882ad fix(cli): no false "[HERMES_HOME fallback] wrong profile" warning on every command
Users who set a sticky profile with `hermes profile use <name>` saw
`[HERMES_HOME fallback] HERMES_HOME is unset but active profile is '<name>'
... Any data this process writes will land in the wrong profile` printed by
EVERY `hermes` invocation (`--version`, `update`, `gateway restart`), even
though the CLI re-homes to that profile a few lines later and nothing lands
in the wrong place.

`main._apply_profile_override` scans argv with `top_level_value_flag_sets()`,
which builds the real parser BEFORE the override sets HERMES_HOME. Since
23036e20a6 the `--no-config` help string spells the config path through
`display_hermes_home()` -> `get_hermes_home()`, and that lookup fires the
one-shot fallback warning for a throwaway help string.

`display_hermes_home()` takes an optional `home`; the parser's `_cfg_path()`
passes `get_process_hermes_home()`, which reads HERMES_HOME without the
warning and, after the override, IS the profile home — so `hermes -p coder
--help` still shows `~/.hermes/profiles/coder/config.yaml`. The genuine
warning (a process that really runs under the wrong home) is untouched.

Fixes #112319
Part of #112839
2026-09-16 17:51:11 -07:00
teknium1 43c477571e Merge origin/main (61154b6f68) into feat/unified-gateway-runtime
1,446 upstream commits; 84 conflicted files resolved per family, porting
every post-branch main fix onto the cutover architecture rather than
dropping it or resurrecting replaced pre-cutover code:

- gateway/cron/kanban/a2a: #111988 hygiene bound, silence-marker rejection,
  UX copy, clarify-card retirement on explicit no-answer, Slack task-card
  publication_suppressed, webhook per-route coalescing (wrapping the durable
  admit_producer), kanban dead-worker last output + fingerprint boot witness,
  migrate-profile-identity control verb, SupervisedTickerThread, api-server
  terminal_run_status; config migration 45 kept from main, ours renumbered 46.
- hermes_cli/hermes_state/tools/tui_gateway: read_only SessionDB alongside
  explicit db_path, sessions_cmd observational flag, #111958 served-profile
  skips service install, #111999 usage source upsert repair, archived Bot Chat
  title release, find_canonical_owner, #111151 serves_routed_profile MCP scope,
  session_search after/before/exclude, server.py hunks into session_registry/
  agent_factory siblings, #111775 runtime_check resolve hook, Bot Chat
  silence rule on delivery reply.
- Desktop/apps/shared: pool modules stay deleted; SSH teardown fencing,
  default-profile + peer-window routing, per-session remote headers,
  server->client requests, generated gateway contract with our owner stamps
  and session.replay_gap retained.
- Ink: canonical WebSocket client kept; storedSid reconnect recovery, server
  request store, generated contract types, plain-language copy ported.
2026-09-16 13:20:14 -07:00
teknium1 23036e20a6 fix(ux): plain-language, actionable user-facing messages (core)
Squashed integration of the user-facing message audit for this surface set.
Full per-finding receipts: /tmp/ux-audit/lanes/*-receipt.md (campaign artifacts).
2026-09-15 04:12:13 -07:00
Alan 1657a1ce2d feat(cli): add --format stream-json for structured JSONL output
Adds a --format flag to hermes chat single-query mode. stream-json
emits newline-delimited JSON events (init, text, tool_use, tool_result,
result envelope with token stats + exit code) to stdout for CI
pipelines and external tooling. Session ID stays on stderr.

Salvaged from PR #12278 by @ProDrifterDK onto current main, including
the follow-up commit enforcing the single-query contract (implies
quiet, rejects --tui, emits a final result record with exit code 130
on interrupt).
2026-09-15 03:53:13 -07:00
Teknium ee6b116050 feat: hermes chat --safe-mode runs as a frozen policy in an isolated worker
The canonical CLI now forwards safe_mode/ignore_user_config as session.create
policy (moved from _UNSUPPORTED to _POLICY, `--base-url`/`--api-key` gain real
chat flags) instead of mutating HERMES_SAFE_MODE/HERMES_IGNORE_USER_CONFIG in
the client environment, where they would ride into an authority spawn.
_prepare_agent_startup/cmd_chat treat a bypass chat as pure transport: no env
mutation, no plugin/MCP/hook discovery, no profile probing or model cost guard
(gateway_chat_startup), no display.interface read. gateway/run.py's import-time
config→env bridge returns the frozen snapshot inside a bypass worker instead of
opening the profile YAML. A bypass launch requires an explicit --model.

tests/hermes_cli/test_safe_mode.py is rewritten from the dead env-mutation
assertion into the live contract (recipe D-F): sentinel plugin/hook/SOUL/rules/
skill in a disposable profile fire on an ordinary turn in the owner; the real
`hermes chat --safe-mode --provider custom --base-url … --model … --api-key …
-Q -q` on a PTY gets the loopback reply, one canonical admission and one
terminal worker_executions row, worker pid != owner pid, zero /plugins/ or
profile YAML/rules/skill opens in the worker (audit hook with stacks), zero
sentinel events for the worker pid, no HERMES_SAFE_MODE in the daemon env;
a malformed config.yaml before the second launch still works and is not
repaired; --ignore-user-config alone still discovers plugins but reads no YAML.
2026-09-09 03:10:45 -07:00
ethernet 8b7eae99ef fix(pm): own interpreter selection and dependency recovery
Pin uv and uvx to the PM interpreter instead of ambient Python discovery.
A matching dependency stamp cannot prove that installed files still exist.
Repair now rebuilds the recorded workspace and lock in a fresh generation,
checks startup imports, and publishes the selection only after success.

Run startup recovery before dependency activation. Keep manual PM repair
reachable when the selected environment is damaged. Preserve plugin
selection, retry ownership, and the previous generation on failure.
Remove the separate pip, ensurepip, per-extra, and install-time quarantine
ladders. Keep orphan launcher restoration.

Verification: 717 targeted tests passed on native Windows ARM64, with
56 skipped. Ruff, diff checks, and the source-scoped compat check passed.
A disposable real Hermes install recovered deleted YAML and dotenv files,
then printed CLI help with exit 0. Its lock and stamp stayed unchanged.
The full suite and a release build were not run for this change.
2026-09-08 23:39:55 -04:00
Brian Le 32a59f3bf7 feat: refresh one pooled OAuth grant from the CLI 2026-09-07 08:06:48 -07:00
Brian Le 1a4bb74a40 feat: choose pooled credential priority from the CLI 2026-09-07 08:06:48 -07:00
Brian Le 53221df05f feat: reset one pooled credential without clearing sibling cooldowns 2026-09-07 08:06:48 -07:00
Teknium e83816a4d1 review-fix(comments): restore lost #NNNN rationale comments across non-test source (mechanical sweep, condensed, code unchanged)
For each issue anchor present in BASE 63279301bc non-test .py and absent on HEAD, the BASE comment/docstring block was re-attached at the HEAD location of the code it explained (matched by the distinctive code line / enclosing def). Sentences already covered by an existing HEAD comment were deduped; the issue number always survives. Insert-only: no code lines changed.
2026-09-03 09:44:26 -07:00
Teknium 20bbe2bae0 refactor(hermes_cli): compact _parser.py argparse construction (help bytes + action attrs identical) 2026-09-02 19:55:05 -07:00
Teknium 6eae28cb6d refactor(hermes_cli): split cmd_chat prologue into helpers; split build_top_level_parser into flags + chat builders 2026-09-02 16:33:36 -07:00
Teknium cfa327e5dc refactor(hclib): remaining hermes_cli library modules — dead code, unified helpers, flattened branches 2026-09-02 15:03:42 -07:00
Teknium a5c7eed5f3 feat(cli/tui): -q now seeds a live interactive session; prompts submit literally
On a real TTY, `hermes chat -q "…"` (and `--tui -q`) now starts a normal
interactive session with the prompt submitted literally as the first turn —
no slash-command routing, no '!' shell dispatch, no $(...) interpolation,
no file-drop rewriting — matching how other coding agents handle seeded
launches (Omarchy prompted agent terminals, basecamp/omarchy#8705).

Legacy answer-and-exit is preserved everywhere automation depends on it:
- new `hermes chat --oneshot` flag (distinct dest from top-level -z)
- -Q/--quiet machine-readable contract
- any non-TTY stdio (kanban workers, cron, pipes, A2A)
- top-level `hermes -z` unchanged

CLI: seeded prompt rides a _SeededQueryMessage sentinel through
process_loop, which skips the slash/!/file-drop dispatchers for that one
message. TUI: STARTUP_QUERY submits via a new literal path (submitLiteral)
that bypasses dispatchSubmission and the input.detect_drop rewrite.
2026-08-28 05:15:06 -07:00
Teknium 905edf37a4 refactor(cli): add static fallback to parser-derived value-flag helper
Mirror the full update_cmd._holder_value_flags precedent: derive both
top-level value-flag sets from build_top_level_parser() with a cached
frozenset, and fall back to a handwritten snapshot if parser
introspection ever fails, so argv classification keeps working on a
broken tree. Parity test pins the derived sets against the live parser
so drift fails CI.

Builds on #93551 (fangliquanflq) and #93570 (aniruddhaadak80) for #93530.
2026-08-24 03:20:37 -07:00
fangliquanflq 694550e486 fix(cli): derive top-level value flags from parser
(cherry picked from commit f2e5a1388115615fafde49a5f2144e5855a81d89)
2026-08-24 03:20:37 -07:00
Teknium 803397ecc3 feat: wall-clock run budget — wrap-up injection at 80% and deadline-scaled stale timeouts (agent.run_budget_seconds / --run-budget) 2026-08-19 16:32:17 -07:00
Teknium 4903993cdd test: enforce -q/--query-file exclusivity at parse time
The subprocess-based exclusivity test invoked hermes_cli.main in the CI
environment where startup exits 1 before the manual guard runs. Enforce
the conflict in argparse itself (mutually exclusive group, exit 2 at parse
time) and test the parser directly; the manual guard stays for programmatic
namespace fills.
2026-08-18 22:31:32 -07:00
Tekniumandmehmetkr-31 1190825652 fix(bot-mode): DM protocol no longer shell-interpolates message bodies
The Bot Mode teammate-DM protocol told agents to inline the message into a
double-quoted shell argument: quotes truncated the body and $(...)/backticks
executed on the sender's machine. The protocol now writes the message to a
temp file and delivers it via a new 'hermes chat --query-file' flag (or '-'
for stdin); 'hermes peer dm' already accepted stdin and the peer recipe now
uses it. No shell pass touches the body at any point.

Supersedes the tool-based approach in #89077 — same bug, fixed with a CLI
flag + protocol rewrite instead of a new model tool.

Co-authored-by: mehmetkr-31 <mehmetkr-31@users.noreply.github.com>
2026-08-18 22:31:32 -07:00
yflmq001 a2fcac087c fix(cli): chat -c fails loudly on stderr and gains --create-if-missing
`hermes chat -c "<title>" -q "<text>"` silently no-oped when no session
matched the title under quiet/programmatic use: the not-found message was
written to stdout (the channel quiet callers parse as the final response),
so a background send to a not-yet-existing named session vanished with no
error. Surfaces via Hermes-Bot-Mode bot-to-bot handoffs (#86794).

- not-found message now goes to stderr (exit 1 unchanged), so programmatic
  callers always see it even with -Q/--quiet
- new --create-if-missing: with `-c <title>` and no matching session, create
  a fresh session carrying the title and proceed — the deterministic
  "send to this named thread, making it if needed" primitive plugins asked for
- extract the -c resolution block into _resolve_continue_arg for testability

Tests: flag parsing, titled-session creation, stderr routing, source guard.
2026-08-16 06:31:26 -07:00
Teknium c5f5fa40c3 feat: --resume latest keyword and --in DIR launch flag
--resume latest resolves the most recent session through the same
workspace-scoped MRU lookup as -c (TUI source first under --tui, with
classic-CLI fallback). --in DIR chdirs before session resolution so the
lookup keys off DIR's workspace, and pins the session there by skipping
the recorded-cwd restore.

Requested by @Jeff9James: hermes --tui --resume latest --in ./dir
2026-08-08 04:03:41 -07:00
Brooklyn Nicholson 0b69a6ac02 feat(kanban): let a task pin its own thinking depth
A task could already pin a model and provider, but not how hard the worker
thinks: reasoning effort came from the assigned profile's config and nothing
per-task could reach it. Pairing a small model with high effort, or a big one
with thinking off, meant editing the worker profile itself.

Adds a tasks.reasoning_effort column (migrated, NULL = inherit the profile)
with set_reasoning_effort(), a create_task kwarg, and a --reasoning spawn flag.
Kept deliberately independent of model_override: a task may run the profile's
own model at a different depth, and clearing a model override no longer resets
the depth the operator chose. "none" is a value (thinking off), not a clear.

--reasoning is new on the CLI too — the level was only reachable through the
/reasoning slash command, so the dispatcher had no flag to pass. It overrides
agent.reasoning_effort for one run and is never persisted.
2026-08-01 16:09:56 -05:00
teknium1 1b081e4891 feat: raise default tool-calling iteration limit from 90 to 500
The default max_iterations/agent.max_turns budget was set when long
agentic runs were rare; complex tasks now routinely exceed 90 tool
calls. Raise the default to 500 across every surface that hardcodes
the fallback: AIAgent constructor, DEFAULT_CONFIG, CLI resolution
chain, gateway env bridge, cron scheduler, and TUI gateway. Explicit
user config values are unaffected (deep-merge preserves them; no
_config_version bump needed).

Docs (en + zh-Hans), CLI help text, tips, and pinned tests updated
to match.
2026-07-26 12:54:45 -07:00
Brooklyn NicholsonandCary Palmer b5f0e451c1 feat(cli): restore cwd on resume (--no-restore-cwd)
Resuming a session cd's back into its recorded working directory, so it resumes
in the repo it belonged to. `--no-restore-cwd` opts out; skipped under
--worktree (that path owns its dir); best-effort — a missing dir warns and stays
put rather than failing the resume.

Co-authored-by: Cary Palmer <palmer@dugoutfantasy.com>
2026-07-12 05:08:13 -04:00
briandevans 5ecc07986f fix(cli): preserve -t/-m/--provider/--tui/--dev before chat subcommand
`hermes -t web chat` silently dropped the toolset filter (and the same
hold true for `-m`, `--provider`, `--tui`, `--dev` placed before
`chat`). Reported in #28780 for `-t/--toolsets`; the others are sibling
failures with the same root cause.

Root cause: the chat subparser re-declared these flags with `default=None`
(or `default=False` for store_true) on top of the matching top-level
parser flags. When argparse dispatches into the subparser it shares the
namespace via `dest`, so the subparser's default overwrites whatever the
top-level parser parsed before the subcommand. `-s/--skills`, `-r/-c/-w`,
`--yolo`, and `--pass-session-id` already use `default=argparse.SUPPRESS`
for exactly this reason — the chat-subparser action becomes a no-op
unless the user explicitly passes the flag after `chat`, and the parent
value survives.

Reproduction (origin/main, before fix):

  >>> parser.parse_known_args(["-t", "web", "chat"]).toolsets
  None
  >>> parser.parse_known_args(["chat", "-t", "web"]).toolsets
  'web'

After fix:

  >>> parser.parse_known_args(["-t", "web", "chat"]).toolsets
  'web'
  >>> parser.parse_known_args(["chat", "-t", "web"]).toolsets
  'web'

Sibling flags fixed in the same commit because they share the exact same
argparse pattern bug — verified via a new contract test that scans every
chat-subparser action whose `dest` is also on the top-level parser and
asserts `default is argparse.SUPPRESS`. The test fails on origin/main
listing all five offenders and passes after this fix.

Test additions in tests/hermes_cli/test_argparse_flag_propagation.py:
- TestChatSubparserInheritedValueFlags exercising real `_parser` build
  (not the hand-rolled replica) so it catches future drift.
- Parametrized before-chat / after-chat cases for `-t`, `--toolsets`,
  `-m`, `--model`, `--provider`.
- Negative case: passing none of the flags leaves attrs at the top-level
  parser's `None` default (SUPPRESS does not remove existing attrs).
- Combined case: all three value flags before `chat` simultaneously.
- store_true cases for `--tui` / `--dev`.
- Contract test asserting every shared-`dest` flag on chat uses SUPPRESS.

Fixes #28780.
2026-07-11 03:51:43 -07:00
Teknium 7dfd5077ce feat(oneshot): add --usage-file JSON usage report to hermes -z (#59615)
* feat(oneshot): add --usage-file JSON usage report to hermes -z

Pipelines driving hermes -z (batch reviewers, cron scripts, eval
harnesses) had no way to account for per-invocation spend: the agent
computes estimated_cost_usd and full token counts internally, but
oneshot mode discards everything except the final response text.

- hermes -z PROMPT --usage-file PATH writes a JSON report after the
  run: estimated_cost_usd, cost_status/source, input/output/cache/
  reasoning/total tokens, api_calls, model, provider, session_id,
  completed, failed.
- Written even when the run fails (with a failure field) so callers
  can always account for spend; the write itself is best-effort and
  never masks the run's own outcome.
- Flag registered in both the full parser and the Termux fast path;
  added to both value-flag scan sets so profile detection stays
  correct.

Validation: 6 unit tests + live E2E (real -z run produced a report
with real OpenRouter cost + token counts).

* test: include usage_file kwarg in oneshot dispatch assertions

The two dispatch tests assert the exact kwargs dict passed to
run_oneshot; the new usage_file kwarg must appear there.
2026-07-06 11:10:51 -07:00
Shannon Sands dcbce869ae Add safe Hermes console REPL 2026-07-03 20:18:00 +05:30
Teknium 8cf9d8689d fix(desktop): keep composer usable during reconnect (#45488)
* feat(cli): add --safe-mode troubleshooting flag

Inspired by Claude Code v2.1.169 (June 2026): run Hermes with all
customizations disabled to isolate setup problems from product bugs.

--safe-mode implies --ignore-user-config and --ignore-rules, and
additionally skips plugin discovery (hermes_cli/plugins.py) and MCP
server loading (tools/mcp_tool.py) via the internal HERMES_SAFE_MODE
env bridge.

* fix(desktop): keep composer usable during reconnect
2026-06-13 02:36:09 -07:00
Brooklyn Nicholson d6b0c23f87 feat(cli): configurable default interface (cli vs tui)
Add `display.interface` config key so users can make the modern TUI the
default for bare `hermes` / `hermes chat` without exporting HERMES_TUI=1 in
every shell. Default stays "cli" to preserve current behavior.

Add a `--cli` flag (mirrors `--tui`) so an explicit invocation can force the
classic prompt_toolkit REPL even when `display.interface: tui` is configured.

Precedence (highest first): `--cli` > `--tui`/`HERMES_TUI=1` > config
`display.interface` > classic REPL. Two resolvers enforce it:

  * `_resolve_use_tui(args)` — the args-aware resolver used by `cmd_chat`
    and the Termux fast-TUI path (uses full load_config()).
  * `_wants_tui_early(argv)` — a dependency-free early resolver used by
    mouse-residue suppression and the Termux fast paths, which run before
    argparse / hermes_cli.config are importable (minimal cached YAML read).

Both `--cli` and `--tui` are registered via `_inherited_flag`, so they are
carried across self-relaunch automatically.

- config: add display.interface ("cli" default), bump _config_version 25->26.
  The generic missing-field migration + load_config() deep-merge seed the key
  for existing configs; no bespoke migration block needed.
- docs: document --cli flag and display.interface in cli-commands.md and
  the TUI user guide.
- tests: new test_default_interface_resolution.py covering resolver
  precedence at every layer, early resolver edge cases (missing/garbage
  config), parser flags, and relaunch inheritance.
2026-06-02 20:49:44 -05:00
honor2030 6a1aa420e7 Fix CLI verbose tool progress config fallback 2026-05-23 21:03:51 -07:00
Teknium e42fcc5625 fix(provider): make config.yaml model.provider the single source of truth (#31222)
Policy: if it ain't a secret it goes in config.yaml. HERMES_INFERENCE_PROVIDER
was leaking behavioral config into the .env surface, including from the gateway,
which bypassed config.yaml entirely.

Behavior:
- gateway/run.py: drop HERMES_INFERENCE_PROVIDER read in _resolve_runtime_agent_kwargs.
  Gateway now flows through resolve_runtime_provider() with no `requested` override,
  which reads model.provider from config.yaml first.

Docs/UX (strip env var from user-facing surface):
- --provider help text no longer mentions the env var
- cli-config.yaml.example same
- reference/environment-variables.md: remove HERMES_INFERENCE_PROVIDER row and
  the cross-reference from HERMES_INFERENCE_MODEL
- reference/cli-commands.md: blank the env-var column for --provider
- guides/xai-grok-oauth.md, guides/minimax-oauth.md: replace
  HERMES_INFERENCE_PROVIDER=x hermes invocations with config.yaml / --provider
- developer-guide/adding-providers.md, model-provider-plugin.md: reframe

Internal mechanism (kept as-is):
- hermes_cli/main.py writes HERMES_INFERENCE_PROVIDER into the TUI subprocess env
- tui_gateway/server.py reads it on TUI startup
- resolve_requested_provider() / oneshot.py / cli.py still fall through to the
  env var as a last-resort behind config.yaml, which is what makes the TUI
  parent->child handoff work
This stays. We just stop documenting it as a user knob.

Tests: tests/gateway/test_auth_fallback.py — simplify mock to fail on first
call, succeed on second; drop monkeypatch.setenv lines that no longer matter.

Supersedes #31064 (closed with credit to @novax635 who surfaced the underlying
issue but proposed aligning gateway *to* the env var rather than removing it).
2026-05-23 18:18:41 -07:00
luoyuctl 2f2f654486 fix: add dashboard to CLI help epilogue and Docker CI smoke test
- Add hermes dashboard examples to the CLI help epilogue so users can
  discover the web UI command from 'hermes --help' output
- Add an independent 'Test dashboard subcommand' CI step that verifies
  'hermes dashboard --help' works in the Docker image, with its own
  mkdir/chown setup to remain independent of the prior smoke test step
- Prevents regressions like #9153 where the dashboard subcommand was
  present in source but missing from the published Docker image

Closes #9153
2026-05-07 06:16:23 -07:00
ethernet 3c673468b4 refactor(cli): derive relaunch flag table from argparse introspection
Pull the top-level + chat parser construction out of main() into
hermes_cli/_parser.py so relaunch.py can introspect parser._actions to
discover which flags exist and whether they take values, instead of
maintaining a parallel hand-rolled (flag, takes_value) tuple list.

- _parser.py: build_top_level_parser() returns (parser, subparsers,
  chat_parser); side-effect-free import.
- main.py: ~290 lines of inline parser construction collapsed to a
  helper call. Other subparsers stay inline (dispatch is bound to
  module-level cmd_* functions).
- _parser._inherited_flag(parser, ...): wraps parser.add_argument and
  sets action.inherit_on_relaunch = True. Used in place of
  parser.add_argument for the 25 flags (top-level + chat) that need to
  carry over.
- _parser.PRE_ARGPARSE_INHERITED_FLAGS: holds --profile/-p, which
  isn't on argparse (consumed earlier by main._apply_profile_override).
- relaunch.py: drops _CRITICAL_DESTS and _PRE_ARGPARSE_FLAGS; the table
  builder now filters by getattr(action, 'inherit_on_relaunch', False).
- test_ignore_user_config_flags.py: brittle inspect.getsource grep
  replaced with proper parser introspection.
- test_relaunch.py: introspection sanity tests added.

Salvaged from PR #17549; added top-level -t/--toolsets flag to
_parser.py so #17623 (fix(tui): honor launch toolsets) behavior is
preserved on current main.

Co-authored-by: ethernet <arilotter@gmail.com>
2026-04-29 20:33:29 -07:00