
    Jjo                     .   U d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddlZddlmZ ddlmZ ddlmZmZmZmZmZmZ ddlmZ  ej        e          Z e            Zee d<   ded	ee         fd
Z!dede"d	dfdZ# ej$                    dk    Z% ej&        d          Z' e(h d          Z)e(e*         e d<   de*d	dfdZ+i Z,ee*ef         e d<   i Z-ee*ee.e.e.e.ee*ef         ee*ee*         f         f         f         e d<   i Z/ee*ee.e.ee*ef         f         f         e d<    ej0                    Z1 e(h d          Z2ddl3Z3ddl4m5Z5m6Z6 ddl7m8Z8m9Z9 dZ:dddddZ;d	ee*         fdZ<d	e=fdZ>dZ?d	ee*         fd Z@dLd!ee         d	efd"ZAdLd!ee         d	e*fd#ZBd	e=fd$ZCdLd%e*d!ee         d	dfd&ZDd	e=fd'ZEd%e*d	e*fd(ZFd	e*fd)ZGd*ZHd	e*fd+ZIdMd-e*d	e*fd.ZJdNd-e*fd0ZKd	eeL         fd1ZMdd2lNmOZO dd3lPmQZQmRZR d	efd4ZSd	efd5ZTd	efd6ZUd	eVee.         ee.         f         fd7ZWdOd8ZXd9 ZYd	e=fd:ZZd; Z[d<ed	dfd=Z\d> Z]d<efd?Z^i d@dAdBi dCg dDi dEdFgdGddHdIdJi dKdLdMdNdOddPdQdRdAdSdTdUdTdVdWdXdWdYdWdZdAd[dTd\dAd]dWd^dQd_dTd`dadbdcdbdTg dddei dfdgdhdTdidjdkdcdldmdng dodTdpg dqdWdrdsdtg dui dvdwdxdsdydsdzd{d|d}d~dWg ddWg ddWdddAdAdAddddddddTdWdAdddddAdAdddddddddddWddWdddddddddddddddWdddQdddddddi ddWdddddQddWdWdWd
dddWidddiddWdddddAdWg dbddAdAdddddi ddddTdAdAdAdi ddddTdAdAdAdi dddTdAdAdAdi dddTdAdAdAdi dddTdAdAdAdi dddTdAdAdAdi dddTdAdAdAdi dAdddTdAdAdAdi dddTdAdAdAdi dddTdAdAdAdci dddTdAdAdAdi dddTdAdAdAdi dddTdAdAdAdi dddTdAdAdAdi dddTdAdAdAdai dddTdAdAdAdai ddi ddddAddГddddddԓddQddWddؓddWddۓddddWddddddddi ddddddddWddԓddWddWddWddWddWddddddddddddddddWdi ddWdi ddddWiddiddg dddTddAdTddd dddddAdAddAdAdAdAddddddAdd	d
diddddiddddddddddAddddddddddAdAdd d!dd"id#	d$dWdWdgd%dAd&d@d'id@d(id)dAddd*d+dd,dddWdԐd-d.d/d0d1d2d3d4d5d6id7dWdWdd8d9dAd:d;dAdAdAdAdAdWd<dddAdQd{dWdd=d>dAi d?dKdid@ddAddAddAdBdCddDdCgddEdCdFdWdGidHdIg dWdddddJddWdKdddLddWdWddLdMdNi dOdAdPdWdAdAi dQdRdWdAdAdWdddWd<dWi dAdAddSddWdAdTdUddWg dVdWdXdYi dZdi dAddd[d\d]dWdAdAi dQd^dWdAdAd_d`dadŐdbg dWdWdcddg dei dfi dgi i dhddii djddWdWdkddWdg g dlg dWdm	dndAdodAdAdAdpdWddd<dqddWdddrd{dAdAddWdQdsdtdudvdwidxdydTddddzid{d|ddQd}d~dWdd{i ddddiddddidQdŐdddiddg dWdǐddid
dddTddddddddLdWddddi dddddddWdddWdddTi ddddcddddddAddWdWdAdidddddig dTddAddddZ_g dddgg ddgdgdZ`ee.ee*         f         e d<   i Zai dddddddWddddddWdgddWddddddWddWddddddWddWddddddddWddÐdĐdŐddddWddǐdȐdȐddWddWddʐdːdddddWdd͐dΐdϐddWddWddѐdҐdddddWddԐdՐdddWddWddאdؐdddddWddڐdېdܐddWddWddސdߐdddWddWdddߐdddWddWddddddddWddddddWddWdi dddddddWddddddWddWddddddWddWddddddddWddddddWddWddddddddWddddddWddWdddd ddddWddddddWddWddddddddWddd	d
ddWddWddddddddWddddddWddddddAddddddddWddddddAdddWdddd ddddWdi d!d"d#d$dWddWdd%d&d'ddddWdd(d)d*d$dWddWdd+d,d-ddddWdd.d/d0d1dWddd2d3d4ddddWdd5d6d7d8dWddWdd9d:d;ddddWdd<d=d>d?dWddd@dAdBddddWddCdDdEdFdddWddGdHdIddddWddJdKdLdMdWdddNdOdPddddWddQdRdSdTdUdgdWdVdWdXdYdZd[dUdgdWdVdWd\d]d^d_dUdgdWdVdWi d`dadbdddVdWddcdddedddVdWddfdgdhdddVdWddidjdkdddVdWddldmdnddWdVdWdddodpdqdUdgdWdVdWdrdsdtdudUgddVdWdvdwdxdydUgdWdVdWdzd{d|d}d~dgdWdVdWdddd}d~dgddVdWddddd~dgdWdVdWdddd~dgddVdddddg dddVdWdddddd~dgddVdWddddd~dgdWdVdWdddddddgdWdVdWdddddgdWdVdWi ddddddgdWdVdWddddddgdWdVdWdddddWdVddddddWdVddddddWddWddddddWddWddddddWddWddddddWddWddĐdŐdƐdǐdgdWdVdWdɐdʐdːdVd̜d͐dΐdϐdАdgdWdVdWdҐdӐdԐdVdWd՜d֐dאdؐdِdgdWdVdWdېdܐdݐdސdgdWdVdWdddddgdWdVdWddddVdWd՜dddddgdWdVdWi ddddgdWdVdddddVdWd՜ddddddVddddddWdVdddddddVdWdddd ddWdddddddddddd	ddd
dddddWddddddddddddddddddddddWddddddddWdddd d!d"ddddd#d$d%d&dddd'd(d)ddWddd*d+d,ddddi d-d.d/ddddd0d1d2ddddd3d4d5d6dddd7d8d9ddWddd:d;d<ddddd=d>d?ddddd@dAdBddddWddCdDdEddddWddFdGdHddddWddIdJdKddddWddLdMdNddddWddOdPdQddWddWddRdSdTdUddddVdWdXddWdddYdZd[ddddd\d]d^dd̜d_d`dadbddci dddedfdWdd
dgdhdidd̜djdkdldd̜dmdndodd̜dpdqdrdd̜dsdtdudd̜dvdwdxdd̜dydzd{ddddd|d}d~ddddddddddddddddWddWddddddWddWddddddddWddddddddWddddddWddWddddddddWddddddddWdddddddWdddddddWdddddWddWdddddddddddddddddWddddddWddddddddddddddd	ZbdPde=d	eee*ef                  fdZcde*fdZddWdWdddee*ef         de=de=de=d	ee*ef         f
dZed	eee*ef                  fdZfd	eee*ef                  fdZg e            Zhee d<   de*de*de*ded	df
dZidAddede*d	eee*ef                  fdZjdAddede*d	eee*ef                  fdZkded	eee*ef                  fdZl	 dLdeee*ef                  d	eee*ef                  fdĄZmded	ee=         fdƄZn	 	 dQde*deeee*ef                           deee*ef                  d	ee*ef         fdɄZo	 	 dQdee*ef         de*deeee*ef                           deee*ef                  d	df
d˄Zpded	ee*e*f         fd̈́Zq	 	 dQde*deeee*ef                           deee*ef                  d	ee*e*f         fd΄Zr	 	 dQdee*ef         de*deeee*ef                           deee*ef                  d	df
dτZs	 	 dQd@e*de*deeee*ef                           deee*ef                  d	ee.         f
dЄZtded	e.fdфZud	ee.e.f         fd҄Zvh dӣZwh dԣZxh dգZye G dք dצ                      ZzdLdeee*ef                  d	ed         fd؄Z{dLdeee*ef                  d	dfdلZ|dLdeee*ef                  d	dfdڄZ}dee*ef         d	dfdۄZ~dRde=de=d	ee*ef         fdބZd%eLdeLd	eLfdZdeLded	eeLef         fdZd ZdLdZd ZdLdZdee*ef         d	eee*df                  fdZe_dfdee*ef         dee*ef         deeee*df                           d	ee*ef         fdZdee*ef         d	ee*ef         fdZdee*ef         d	ee*ef         fdZdddeee*ef                  de*ded	efdZd	ee*ef         fdZdLdee         d	dfdZdededed	dfdZd	ee*ef         fdZd	ee*ef         fdZddde*de*dede=d	df
dZi dfddhddiddkd dddrddtddvddxddyddd	d
ddddddzdd|dddddddddddddddZded	e*fd Zde*d	ee*         fd!Zdddd"d#eee*e*f                  deee*ef                  dee=         d	ee*e*f         fd$Zd%e=d	ee*ef         fd&Zd'Zd(Zd)ZdWdd*dee*ef         d+e=deeee*df                           fd,Zd-e*d	e*fd.Zd	ee*e*f         fd/Zdaeeee*ee         ee.         f         ee*e*f         f                  e d0<   dOd1Zd2Zde*d	e=fd3Zd4ed	efd5Zd	e.fd6Zde*de*d	e*fd7Zde*d	e*fd8Zde*de*fd9Zde*d	e=fd:ZdLde*fd;ZdLd<ZdLde*fd=Zde*de*d	ee*ef         fd>Zd	e.fd?Zde*d	ee*         fd@Zde*d	ee*         fdAZde*d	e*fdBZ e(h dC          ZdSdedDe.d	efdEZdF ZdG Zde*de*fdHZdI ZdadOdJZ e             dadOdKZ e             dS (T  a  
Configuration management for Hermes Agent.

Config files are stored in ~/.hermes/ for easy access:
- ~/.hermes/config.yaml  - All settings (model, toolsets, terminal, etc.)
- ~/.hermes/.env         - API keys and secrets

This module provides:
- hermes config          - Show current configuration
- hermes config edit     - Open config in editor
- hermes config set      - Set a specific value
- hermes config wizard   - Re-run setup wizard
    N)	dataclass)Path)DictAnyOptionalListTupleSet)masked_secret_prompt_CONFIG_PARSE_WARNEDconfig_pathreturnc                 *   	 |                                  rdS |                                 }|j        dk    rdS t          j        d          }|                     | j         d| d          }t          | j        	                    | j         d                    }|D ]8}	 |                                j        |j        k    r dS )# t          $ r Y 5w xY w|                                rdS t          j        | |           |S # t          $ r Y dS w xY w)u  Preserve a corrupted ``config.yaml`` by copying it to a timestamped ``.bak``.

    When the YAML can't be parsed, ``load_config()`` silently falls back to
    ``DEFAULT_CONFIG`` and the user's broken file stays on disk untouched.
    That file is still the user's only copy of their intended overrides — if
    they re-run the setup wizard or ``hermes config set`` (which rewrites
    ``config.yaml``), the broken-but-recoverable content is gone for good.

    This snapshots the corrupted file to ``config.yaml.corrupt.<ts>.bak`` so
    the user can diff/repair it. Unlike Gemini CLI's policy-file recovery
    (which resets the live file to a clean state), we deliberately leave
    ``config.yaml`` in place: hermes never silently mutates the user's config,
    and leaving it means a hand-fixed file is re-read on the next load. The
    backup is best-effort — any failure (permissions, symlink, disk full) is
    swallowed so config loading is never blocked by backup problems.

    Returns the backup path on success, else ``None``. Symlinks are not
    followed/copied (mirrors the Gemini #21541 lstat guard) to avoid
    clobbering whatever a malicious/misconfigured symlink points at.
    Nr   z%Y%m%d-%H%M%Sz	.corrupt.z.bakz.corrupt.*.bak)
is_symlinkstatst_sizetimestrftime	with_namenamelistparentglobOSErrorexistsshutilcopy2	Exception)r   sttsbackup_pathsibling_baksexistings         7/home/rurouni/.hermes/hermes-agent/hermes_cli/config.py_backup_corrupt_configr%   *   s[   *!!## 	4:?? 4]?++!++{/?,R,R",R,R,RSS
 ##{'7$G$G$GHH
 
 % 	 	H==??*bj88  44 9     	4[+...   ttsL   D D A'D !"CD D 
CD CD -D 
DDexcc                    	 |                                  }t          |           |j        |j        f}n"# t          $ r t          |           ddf}Y nw xY w|t
          v rdS t
                              |           t          |           }d|  d| d}|	|d| dz  }t          	                    |           	 t          j                            d| d	           t          j                                         dS # t          $ r Y dS w xY w)
a  Surface a config.yaml parse failure to user, log, and stderr.

    A YAML parse error in ``~/.hermes/config.yaml`` causes ``load_config()``
    to silently fall back to ``DEFAULT_CONFIG``, which means every user
    override (auxiliary providers, fallback chain, model overrides, etc.)
    is dropped. Before this helper that was a one-line ``print(...)`` that
    scrolled off-screen on the first invocation and was never seen again.

    Now: warn once per (path, mtime_ns, size) on stderr **and** in
    ``agent.log`` / ``errors.log`` at WARNING level so ``hermes logs``
    surfaces it. Re-warns automatically if the file changes (different
    mtime/size), so users editing the config see the next failure. On the
    first warning for a given broken file we also snapshot it to a
    timestamped ``.bak`` (best-effort) so the user's recoverable content
    survives any later rewrite of ``config.yaml`` by the setup wizard or
    ``hermes config set``.
    r   NzFailed to parse : u   . Falling back to default config — every user override (auxiliary providers, fallback chain, model settings) is being IGNORED. Fix the YAML and restart.z+ A copy of the corrupted file was saved to .u   ⚠️  hermes config: 
)r   strst_mtime_nsr   r   r   addr%   loggerwarningsysstderrwriteflushr   )r   r&   r   keyr!   msgs         r$   _warn_config_parse_failurer6   `   sQ   $';< ' ' ';A&'
"""S!!!(55K	%; 	% 	%# 	% 	% 	%  K[KKKK
NN3
:3:::;;;
   s"   03 AA8AC; ;
D	D	Windowsz^[A-Za-z_][A-Za-z0-9_]*$>   PATHPAGERSHELLEDITORVISUALBROWSERLD_AUDITLD_DEBUG	GIT_SHELL	NODE_PATH
HERMES_ENV
LD_PRELOAD
PYTHONHOME
PYTHONPATHHERMES_HOMENODE_OPTIONSGIT_EXEC_PATHHERMES_CONFIGPYTHONSTARTUPHERMES_PROFILEPYTHONUSERBASEGIT_SSH_COMMANDLD_LIBRARY_PATHPYTHONEXECUTABLEPYTHONNOUSERSITEDYLD_LIBRARY_PATHDYLD_FRAMEWORK_PATHDYLD_INSERT_LIBRARIESDYLD_FALLBACK_LIBRARY_PATHDYLD_FALLBACK_FRAMEWORK_PATH_ENV_VAR_NAME_DENYLISTr4   c                 >    | t           v rt          d| d          dS )zRaise if ``key`` is in :data:`_ENV_VAR_NAME_DENYLIST`.

    Centralised so both the regular and "secure" env writers share the
    same gate, and so the message is consistent for callers.
    zEnvironment variable a   is on the writer denylist. Names that influence subprocess execution (LD_PRELOAD, PYTHONPATH, PATH, EDITOR, ...) or Hermes runtime location (HERMES_HOME, HERMES_PROFILE, ...) cannot be persisted via the env writer. If you really need this, edit ~/.hermes/.env directly.N)rV   
ValueError)r4   s    r$   _reject_denylisted_env_varrY      s@     $$$'C ' ' '
 
 	
 %$    _LAST_EXPANDED_CONFIG_BY_PATH_LOAD_CONFIG_CACHE_RAW_CONFIG_CACHE>g   IRC_PORT	QQ_APP_ID
IRC_SERVERIRC_CHANNELIRC_USE_TLSIRC_NICKNAMEQQ_STT_MODELTERMINAL_ENVWECOM_BOT_IDWECOM_SECRETWEIXIN_TOKENFEISHU_APP_IDWHATSAPP_MODEOPENAI_API_KEYQQ_STT_API_KEYSIGNAL_ACCOUNTANTHROPIC_TOKENMATRIX_PASSWORDOPENAI_BASE_URLQQ_HOME_CHANNELQQ_STT_BASE_URLSIGNAL_HTTP_URLWEIXIN_BASE_URLMATRIX_DEVICE_IDMATRIX_HOME_ROOMQQ_ALLOWED_USERSQQ_CLIENT_SECRETSMS_HOME_CHANNELTERMINAL_SSH_KEYWEIXIN_DM_POLICYWHATSAPP_ENABLEDANTHROPIC_API_KEYFEISHU_APP_SECRETLANGFUSE_BASE_URLMATRIX_ENCRYPTIONTERMINAL_SSH_PORTWEIXIN_ACCOUNT_IDDINGTALK_CLIENT_IDFEISHU_ENCRYPT_KEYMATRIX_AUTO_THREADQQBOT_HOME_CHANNELQQ_ALLOW_ALL_USERSSLACK_HOME_CHANNELWECOM_HOME_CHANNELFEISHU_HOME_CHANNELHERMES_LANGFUSE_ENVIRC_SERVER_PASSWORDLANGFUSE_PUBLIC_KEYLANGFUSE_SECRET_KEYMATRIX_RECOVERY_KEYQQ_MARKDOWN_SUPPORTSIGNAL_HOME_CHANNELWECOM_CALLBACK_HOSTWECOM_CALLBACK_PORTWEIXIN_CDN_BASE_URLWEIXIN_GROUP_POLICYWEIXIN_HOME_CHANNELBLUEBUBBLES_PASSWORDDISCORD_HOME_CHANNELHERMES_TOOL_PROGRESSQQ_HOME_CHANNEL_NAMESIGNAL_ALLOWED_USERSWECOM_CALLBACK_TOKENWEIXIN_ALLOWED_USERSYUANBAO_HOME_CHANNELDINGTALK_HOME_CHANNELHERMES_LANGFUSE_DEBUGIRC_NICKSERV_PASSWORDMATRIX_DM_AUTO_THREADMATTERMOST_REPLY_MODESMS_HOME_CHANNEL_NAMETELEGRAM_HOME_CHANNELBLUEBUBBLES_SERVER_URLDINGTALK_CLIENT_SECRETMATRIX_REQUIRE_MENTIONQQ_GROUP_ALLOWED_USERSWECOM_CALLBACK_CORP_IDWEIXIN_ALLOW_ALL_USERSHERMES_LANGFUSE_RELEASEMATTERMOST_HOME_CHANNELQQBOT_HOME_CHANNEL_NAMESLACK_HOME_CHANNEL_NAMEWECOM_CALLBACK_AGENT_IDWECOM_HOME_CHANNEL_NAMEBLUEBUBBLES_HOME_CHANNELFEISHU_HOME_CHANNEL_NAMESIGNAL_HOME_CHANNEL_NAMEWEIXIN_HOME_CHANNEL_NAMEDISCORD_HOME_CHANNEL_NAMEFEISHU_VERIFICATION_TOKENHERMES_LANGFUSE_MAX_CHARSHERMES_TOOL_PROGRESS_MODEYUANBAO_HOME_CHANNEL_NAMEDINGTALK_HOME_CHANNEL_NAMEMATRIX_FREE_RESPONSE_ROOMSSIGNAL_GROUP_ALLOWED_USERSTELEGRAM_HOME_CHANNEL_NAMEWECOM_CALLBACK_CORP_SECRETWEIXIN_GROUP_ALLOWED_USERSHERMES_LANGFUSE_SAMPLE_RATEMATTERMOST_HOME_CHANNEL_NAMEBLUEBUBBLES_HOME_CHANNEL_NAMEWECOM_CALLBACK_ENCODING_AES_KEY)Colorscolor)DEFAULT_SOUL_MDis_legacy_template_soul)true1yesHomebrewNixOS)brewhomebrewnixnixosc                     t          j        dd                                          } | r:|                                 }|t          v rdS t
                              ||           S t                      dz  }|                                rdS dS )z7Return the package manager owning this install, if any.HERMES_MANAGED r   z.managedN)	osgetenvstriplower_MANAGED_TRUE_VALUES_MANAGED_SYSTEM_NAMESgetget_hermes_homer   )raw
normalizedmanaged_markers      r$   get_managed_systemr   @  s    
)$b
)
)
/
/
1
1C
 :YY[[
---7$((S999$&&3N w4rZ   c                  "    t                      duS )a  Check if Hermes is running in package-manager-managed mode.

    Two signals: the HERMES_MANAGED env var (set by the systemd service),
    or a .managed marker file in HERMES_HOME (set by the NixOS activation
    script, so interactive shells also see it).
    N)r    rZ   r$   
is_managedr   O  s     t++rZ   zfUpdate your Nix flake input and rebuild (e.g. nix flake update, nixos-rebuild, or home-manager switch)c                  L    t                      } | dk    rdS | dk    rt          S dS )z;Return the preferred upgrade command for a managed install.r   brew upgrade hermes-agentr   N)r   _NIX_UPDATE_MSG)managed_systems    r$   get_managed_update_commandr   \  s4    '))N##**  4rZ   project_rootc                 j    | | S t          t                    j        j                                        S )u  Resolve the directory that holds the *running code* (the install tree).

    This is the parent of ``hermes_cli/`` — i.e. the git checkout for source
    installs, ``/opt/hermes`` inside the published image, the venv's
    site-packages root for pip installs. It is a property of the running
    interpreter, NOT of ``$HERMES_HOME``, which is why a code-scoped stamp
    here is immune to two installs sharing one data directory.
    r   __file__r   resolve)r   s    r$   _install_method_project_rootr   f  s.     >> '//111rZ   c                 R   t          |           }	 |dz                      d                                                                          }|r|S n# t          $ r Y nw xY w	 t                      dz                      d                                                                          }|r|dk    rt                      r|S n# t          $ r Y nw xY wt                      }|r(|                                                    dd          S |dz  	                                rdS d	S )
u	  Detect how Hermes was installed: 'docker', 'nixos', 'homebrew', 'git', or 'pip'.

    Resolution order:
    1. Code-scoped stamp ``<install tree>/.install_method`` (next to the
       running code) — the authoritative marker.
    2. Legacy home-scoped stamp ``$HERMES_HOME/.install_method`` — read for
       backward compatibility, but a ``docker`` value is IGNORED when we are
       not actually running inside a container (see below).
    3. HERMES_MANAGED env / .managed marker (NixOS, Homebrew)
    4. .git directory presence -> 'git'
    5. Fallback -> 'pip'

    Why the stamp is code-scoped, not home-scoped (issue: shared ``~/.hermes``)
    --------------------------------------------------------------------------
    The install method describes *the binary that is running*, but
    ``$HERMES_HOME`` is a shared DATA directory — the Docker docs deliberately
    bind-mount it (``~/.hermes:/opt/data``) so config/sessions/memory persist
    and can be shared with a host-side Desktop/CLI install. When a
    containerised gateway and a host install share one ``$HERMES_HOME``, a
    home-scoped stamp is a single slot describing two different installs:
    the container stamps ``docker`` on every boot, the host install then reads
    ``docker`` and ``hermes update`` refuses to run ("doesn't apply inside the
    Docker container") even though the host binary is a perfectly updatable
    git/pip install. Scoping the stamp to the install tree gives each install
    its own truthful marker.

    Self-healing for already-poisoned homes: a legacy ``docker`` value in the
    home-scoped stamp is only honoured when we are genuinely in a container.
    On a host install that read a contaminating ``docker`` stamp, we fall
    through to managed/.git/pip detection instead — so existing shared-home
    setups recover without the user touching anything.

    Note: running inside a container is NOT treated as "docker" on its own.
    The supported installs self-identify via the code-scoped stamp:
      - the curl installer (scripts/install.sh, the README/website install
        command) git-clones the repo and stamps ``git`` next to the code;
      - the published ``nousresearch/hermes-agent`` image bakes a ``docker``
        stamp into ``/opt/hermes`` at build time.
    An unsupported manual install dropped into a container (no stamp) falls
    through to the ``.git``/pip checks and behaves like any off-path install.
    See issue #34397.
    .install_methodutf-8encodingdocker -z.gitgitpip)
r   	read_textr   r   r   r   _running_in_containerr   replaceis_dir)r   rootmethodmanageds       r$   detect_install_methodr   t  sR   V (55D**55w5GGMMOOUUWW 	M	   
!22YY((UWWUWW	 	  	6X--6K6M6M-M    !""G 1}}&&sC000v u5s%   A A 
A A $A C 
CCc                  F    	 ddl m}   |             S # t          $ r Y dS w xY w)zDThin wrapper around ``hermes_constants.is_container`` (import-safe).r   is_containerF)hermes_constantsr  r   r  s    r$   r   r     sG    111111|~~   uu    
  r   c                     t          |          }	 |                    dd           |dz                      | dz   d           dS # t          $ r Y dS w xY w)a<  Write the install method next to the running code (code-scoped stamp).

    The stamp lives in the install tree (``<install tree>/.install_method``),
    not in ``$HERMES_HOME``, so that two installs sharing one data directory
    do not overwrite each other's marker. See ``detect_install_method`` for
    the full rationale.

    Best-effort: if the install tree is read-only (e.g. the immutable
    ``/opt/hermes`` in the published image, which instead bakes the stamp at
    build time) the write silently no-ops and detection falls back to its
    other signals.
    Tparentsexist_okr   r*   r   r   N)r   mkdir
write_textr   )r   r   r   s      r$   stamp_install_methodr    sw     (55D

4$
///	!	!--ftmg-NNNNN   s   4A 
AAc                      dt           dt          fd}  | t          j                  rdS  | t          j        pd          rdS dS )a  Return True when the *running* Hermes lives in a ``uv tool`` layout.

    ``uv tool install hermes-agent`` places the install at
    ``.../uv/tools/hermes-agent/...`` (default ``~/.local/share/uv/tools``,
    or ``$UV_TOOL_DIR/...``). Such installs live outside any virtualenv, so
    ``uv pip install`` fails with ``No virtual environment found`` and the
    update path must use ``uv tool upgrade`` instead.

    Detection is intentionally restricted to properties of the running
    interpreter (``sys.prefix`` / ``sys.executable``). We deliberately do
    NOT consult ``uv tool list``: it would also return True when
    ``hermes-agent`` happens to be uv-tool-installed on the machine while
    the *active* Hermes is a regular pip/venv install, causing
    ``hermes update`` to upgrade the wrong copy. It would also block on a
    subprocess call (~seconds) just to compute a recommendation string.
    pathr   c                     t           j                            |                               t           j        d                                          }d|dz   v S )N/z/uv/tools/hermes-agent/)r   r  normpathr   sepr   )r  norms     r$   _has_uv_tool_markerz/is_uv_tool_install.<locals>._has_uv_tool_marker  sD    w%%--bfc::@@BB(D3J66rZ   Tr   F)r+   boolr0   prefix
executable)r  s    r$   is_uv_tool_installr    sd    "7# 7$ 7 7 7 7 3:&& t3>/R00 t5rZ   c                     | dk    rt           S | dk    rdS | dk    rdS | dk    r(t                      rdS dd	l} |j        d
          rdS dS dS )zAReturn the update command or guidance for a given install method.r   r   r   r   z,docker pull nousresearch/hermes-agent:latestr   zuv tool upgrade hermes-agentr   Nuvz%uv pip install --upgrade hermes-agentz"pip install --upgrade hermes-agentzhermes update)r   r  r   which)r   r   s     r$   %recommended_update_command_for_methodr    s    **== 	2116< 	;::33?rZ   c                  `    t                      } | r| S t                      }t          |          S )z<Return the best update command for the current installation.)r   r   r  )managed_cmdr   s     r$   recommended_update_commandr    s4    ,..K "$$F0888rZ   u  ✗ ``hermes update`` doesn't apply inside the Docker container.

Hermes Agent runs as a published image (nousresearch/hermes-agent), not a
git checkout — the container has no working tree to pull into.  Update by
pulling a fresh image and restarting your container instead:

  docker pull nousresearch/hermes-agent:latest
  # then restart whatever started the container, e.g.:
  docker compose up -d --force-recreate hermes-agent
  # or, for ad-hoc runs, exit the current container and `docker run` again

Verify the new version after restart:
  docker run --rm nousresearch/hermes-agent:latest --version

Notes:
  • If you pinned a specific tag (e.g. ``:v0.14.0``) the ``:latest`` tag
    won't move your container — pull the newer tag you actually want, or
    switch to ``:latest`` / ``:main`` for rolling updates.  See available
    tags at https://hub.docker.com/r/nousresearch/hermes-agent/tags
  • Your config and session history live under ``$HERMES_HOME`` (``/opt/data``
    in the container, typically bind-mounted from the host) and persist
    across image upgrades — re-pulling doesn't lose any state.
  • Running a fork?  Build your own image with this repo's ``Dockerfile``
    and replace the ``docker pull`` step with your build/push pipeline.c                      t           S )a  Return the user-facing message for ``hermes update`` inside Docker.

    Centralised so ``cmd_update`` (the apply path) and ``_cmd_update_check``
    (the dry-run path) share the same wording.  See ``_DOCKER_UPDATE_MESSAGE``
    above for the full rationale.
    )_DOCKER_UPDATE_MESSAGEr   rZ   r$   format_docker_update_messager"  A  s
     "!rZ   modify this Hermes installationactionc                    t                      pd}t          j        dd                                                                          }|dk    r|t
          v rdn|pd}d|  d| dS |d	k    r|pd
}d|  d| dS d|  d| dS )z/Build a user-facing error for managed installs.za package managerr   r   r   r   zCannot z?: this Hermes installation is managed by NixOS (HERMES_MANAGED=ze).
Edit services.hermes-agent.settings in your configuration.nix and run:
  sudo nixos-rebuild switchr   r   zB: this Hermes installation is managed by Homebrew (HERMES_MANAGED=z#).
Use:
  brew upgrade hermes-agentz): this Hermes installation is managed by z:.
Use your package manager to upgrade or reinstall Hermes.)r   r   r   r   r   r   )r$  r   r   env_hints       r$   format_managed_messager'  K  s    '))@-@N
)$b
)
)
/
/
1
1
7
7
9
9C   $88866cmV*f * *'* * *	
 ##$**f * *'* * *	
	C& 	C 	C> 	C 	C 	CrZ   modify configurationc                 V    t          t          |           t          j                   dS )z+Print user-friendly error for managed mode.fileN)printr'  r0   r1   )r$  s    r$   managed_errorr-  g  s&    	
 
(
(sz::::::rZ   c                     t           j                            d          dk    rdS ddlm}   |             rdS t                      dz  }	 i }t          |dd	          5 }|D ]q}|                                }d
|v rW|                    d          sB|	                    d
          \  }}}|                                ||                                <   r	 ddd           n# 1 swxY w Y   n# t          $ r Y dS w xY w|                    dd          }|                    dd          }	|                    dd          }
|                    dd          }||	|
|dS )a  Read container mode metadata from HERMES_HOME/.container-mode.

    Returns a dict with keys: backend, container_name, exec_user, hermes_bin
    or None if container mode is not active, we're already inside the
    container, or HERMES_DEV=1 is set.

    The .container-mode file is written by the NixOS activation script when
    container.enable = true. It tells the host CLI to exec into the container
    instead of running locally.
    
HERMES_DEVr   Nr   r  z.container-moderr   r   =#backendr   container_namezhermes-agent	exec_userhermes
hermes_binz /data/current-package/bin/hermes)r3  r4  r5  r7  )r   environr   r  r  r   openr   
startswith	partitionFileNotFoundError)r  container_mode_fileinfofliner4   _valuer3  r4  r5  r7  s               r$   get_container_exec_inforC  p  s    
z~~l##s**t------|~~ t)++.??	%sW=== 	6 6 6zz||$;;ts';';;$(NN3$7$7MCE(-D%	6	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6 	6    tt hhy(++GXX.??Nh//I,(JKKJ ( 	  s7   
C, A5C C,  C$$C, 'C$(C, ,
C:9C:r   )atomic_replacefast_safe_loadc                  $    t                      dz  S )zGet the main config file path.config.yamlrD  r   rZ   r$   get_config_pathrI    s    },,rZ   c                  $    t                      dz  S )z&Get the .env file path (for API keys)..envrD  r   rZ   r$   get_env_pathrL    s    v%%rZ   c                  b    t          t                    j        j                                        S )z'Get the project installation directory.r   r   rZ   r$   get_project_rootrN    s     >> '//111rZ   c                     t           j        dk    rdS t          j                            dd                                          } t          j                            dd                                          }	 | rt          |           nd}n# t          $ r d}Y nw xY w	 |rt          |          nd}n# t          $ r d}Y nw xY w||fS )a  Read the HERMES_UID / HERMES_GID env vars set by Docker deployments.

    Docker containers running Hermes commonly set these to map the in-container
    user to a host user so volume-mounted state files end up with the right
    ownership. The entrypoint chowns the top-level HERMES_HOME once, but
    subdirectories created at runtime by ``ensure_hermes_home()`` (especially
    for profile namespaces under ``profiles/<name>/``) need the same chown
    or they land as ``root:root`` and block subsequent uid-mapped workers
    with ``PermissionError [Errno 13]``. See #34107.

    Returns ``(uid, gid)`` parsed from the env vars, or ``(None, None)``
    when either is missing/invalid. Returns ``(None, None)`` on Windows
    too (where chown is a no-op anyway).
    win32NN
HERMES_UIDr   
HERMES_GIDN)r0   platformr   r8  r   r   intrX   )uid_strgid_struidgids       r$   _resolve_hermes_uid_gidrZ    s     |wzjnn\2..4466Gjnn\2..4466G%/c'lll4   %/c'lll4   8Os$   8B BBB3 3CCc                     t                      \  }}||dS 	 t          j        | ||nd||nd           dS # t          t          t
          f$ r Y dS w xY w)u  Chown ``path`` to ``HERMES_UID:HERMES_GID`` if those env vars are set.

    No-op when:
      - Either env var is unset/invalid
      - The current process isn't root (chown will EPERM — silently ignored)
      - On Windows (chown semantics don't apply)

    Used by :func:`_secure_dir` to keep ownership consistent across all
    directories created by :func:`ensure_hermes_home` on Docker deployments.
    See #34107.
    N)rZ  r   chownr   AttributeErrorNotImplementedError)r  rX  rY  s      r$   _chown_to_hermes_uidr`    s     '((HC
{s{
?CC?CC	
 	
 	
 	
 	

 ^%89    		s   9 AAc                 T   t                      rdS 	 t          j                            dd                                          }|rt          |d          nd}n# t          $ r d}Y nw xY w	 t          j        | |           n# t          t          f$ r Y nw xY wt          |            dS )uX  Set directory to owner-only access (0700 by default). No-op on Windows.

    Skipped in managed mode — the NixOS module sets group-readable
    permissions (0750) so interactive users in the hermes group can
    share state with the gateway service.

    The mode can be overridden via the HERMES_HOME_MODE environment variable
    (e.g. HERMES_HOME_MODE=0701) for deployments where a web server (nginx,
    caddy, etc.) needs to traverse HERMES_HOME to reach a served subdirectory.
    The execute-only bit on a directory permits cd-through without exposing
    directory listings.

    Also applies ``HERMES_UID``/``HERMES_GID``-based ownership when those env
    vars are set (#34107 — Docker deployments need this so profile subdirs
    created at runtime by kanban workers don't land as root:root and block
    subsequent uid-mapped workers).
    NHERMES_HOME_MODEr      i  )r   r   r8  r   r   rU  rX   chmodr   r_  r`  )r  mode_strmodes      r$   _secure_dirrg    s    $ || :>>"4b99??AA#+6s8Q   
t()   s$   AA A('A(,B BBc                     t           j                            d          st           j                            d          rdS t           j                            d          rdS 	 t          ddd          5 } |                                 }d	d	d	           n# 1 swxY w Y   d
|v sd|v sd|v rdS n# t          t          f$ r Y nw xY wdS )a3  Detect if we're running inside a Docker/Podman/LXC container.

    When Hermes runs in a container with volume-mounted config files, forcing
    0o600 permissions breaks multi-process setups where the gateway and
    dashboard run as different UIDs or the volume mount requires broader
    permissions.
    HERMES_CONTAINERHERMES_SKIP_CHMODTz/.dockerenvz/proc/1/cgroupr0  r   r   Nr   lxckubepodsF)	r   r8  r   r  r   r9  readr   IOError)r?  cgroup_contents     r$   _is_containerrp    s!    
z~~()) RZ^^<O-P-P t	w~~m$$ t"C'::: 	&aVVXXN	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	&~%%.)@)@JR`D`D`4 EaW   5s6   #B0 5B
B0 BB0 BB0 0CCc                     t                      st                      rdS 	 t          j                            t          |                     rt          j        | d           dS dS # t          t          f$ r Y dS w xY w)uQ  Set file to owner-only read/write (0600). No-op on Windows.

    Skipped in managed mode — the NixOS activation script sets
    group-readable permissions (0640) on config files.

    Skipped in containers — Docker/Podman volume mounts often need broader
    permissions.  Set HERMES_SKIP_CHMOD=1 to force-skip on other systems.
    Ni  )	r   rp  r   r  r   r+   rd  r   r_  )r  s    r$   _secure_filerr  %  s     || } 7>>#d))$$ 	"HT5!!!!!	" 	"()   s   AA% %A:9A:homec                    | dz  }|                                 rA	 |                    d          }n# t          t          f$ r Y dS w xY wt	          |          sdS |                    t          d           t          |           dS )a  Seed a default SOUL.md into HERMES_HOME, upgrading legacy empty templates.

    First run: write DEFAULT_SOUL_MD. Existing installs whose SOUL.md is still
    the old comment-only scaffold (seeded by older install.sh / install.ps1 /
    docker images, which shadowed the runtime default) get upgraded in place to
    DEFAULT_SOUL_MD. A SOUL.md the user actually customized is never touched.
    zSOUL.mdr   r   N)r   r   r   UnicodeDecodeErrorr   r  r   rr  )rs  	soul_pathr#   s      r$   _ensure_default_soul_mdrw  7  s     y I 	 **G*<<HH+, 	 	 	FF	&x00 	F7;;;s   2 AAc                     t                      } | j        j        dk    r'|                                 st	          d|  d          t                      rSt          j        d          }	 t          |            t          j        |           dS # t          j        |           w xY w| 	                    dd           t          |            dD ]-}| |z  }|	                    dd           t          |           .t          |            dS )	a   Ensure ~/.hermes directory structure exists with secure permissions.

    In managed mode (NixOS), dirs are created by the activation script with
    setgid + group-writable (2770). We skip mkdir and set umask(0o007) so
    any files created (e.g. SOUL.md) are group-writable (0660).
    profilesz#Named profile home does not exist: z0. Create the profile explicitly before using it.   Tr  )
cronsessionslogszlogs/curatormemoriespairinghooksimage_cacheaudio_cacheskillsN)r   r   r   r   r<  r   r   umask_ensure_hermes_home_managedr
  rg  rw  )rs  	old_umasksubdirds       r$   ensure_hermes_homer  L  s'    D
 {:%%dkkmm%=$ = = =
 
 	
 || &HUOO		 '---HYBHY

4$
///D
 	 	F vAGGD4G000NNNN%%%%%s   )B B$c                    |                                  st          d|  d          dD ]-}| |z  }|                                 st          | d          .| dz  dz                      dd           t          |            dS )	zPManaged-mode variant: verify dirs exist (activation creates them), seed SOUL.md.zHERMES_HOME z7 does not exist. Run 'sudo nixos-rebuild switch' first.)r{  r|  r}  r~  r}  curatorTr  N)r   RuntimeErrorr
  rw  )rs  r  r  s      r$   r  r  p  s    ;;== 
54 5 5 5
 
 	
 ;  6Mxxzz 	 9 9 9  	 
F]Y%%dT%BBBD!!!!!rZ   modelr   	providersfallback_providerscredential_pool_strategiestoolsetsz
hermes-climax_concurrent_sessionsmax_live_sessions   agent	max_turnsZ   gateway_timeouti  restart_drain_timeoutapi_max_retries   service_tiertool_use_enforcementautointent_ack_continuationtask_completion_guidanceTparallel_tool_call_guidanceenvironment_probeenvironment_hintcoding_contextcoding_instructionsverify_guidancemax_verify_nudgesverify_on_stopgateway_timeout_warningi  i     )clarify_timeoutgateway_notify_intervalgateway_auto_continue_freshnessimage_input_modedisabled_toolsetsterminalr3  local
modal_modecwdr)   timeoutdaemon_term_grace_secondsg       @env_passthrough	home_modeshell_init_filesauto_source_bashrcdocker_image*nikolaik/python-nodejs:python3.11-nodejs20docker_forward_env
docker_envsingularity_image3docker://nikolaik/python-nodejs:python3.11-nodejs20modal_imagedaytona_imagecontainer_cpu   container_memoryi   i   F)container_diskcontainer_persistentdocker_volumesdocker_mount_cwd_to_workspacedocker_networkdocker_extra_argsdocker_run_as_host_userpersistent_shellwebi:  )r3  search_backendextract_backendextract_char_limitbrowserx      must_respondi,  zhost.docker.internal)managed_persistenceuser_idsession_keyadopt_existing_tabrewrite_loopback_urlsloopback_host_alias)inactivity_timeoutcommand_timeoutrecord_sessionsallow_private_urlsengineauto_local_for_private_urlscdp_urlallow_unsafe_evaluatedialog_policydialog_timeout_scamofoxcheckpoints   i  
   rz     )enabledmax_snapshotsmax_total_size_mbmax_file_size_mb
auto_pruneretention_daysdelete_orphansmin_interval_hourscontext_file_max_charsfile_read_max_charsi mcp_discovery_timeoutg      ?tool_outputiP  i  )	max_bytes	max_linesmax_line_lengthtool_loop_guardrails   )exact_failuresame_tool_failureidempotent_no_progress   rc  )warnings_enabledhard_stop_enabled
warn_afterhard_stop_aftercompression      ?皙?i  )
r  	thresholdtarget_ratioprotect_last_nhygiene_hard_message_limitprotect_first_nabort_on_summary_failurecodex_gpt55_autoraisecodex_gpt55_autoraise_noticein_placekanbanauto_subscribe_on_createprompt_caching	cache_ttl5m
openrouterg?)response_cacheresponse_cache_ttlmin_coding_scorebedrock)r  provider_filterrefresh_intervalasyncdisabled)guardrail_identifierguardrail_versionstream_processing_modetrace)region	discovery	guardrail	auxiliarytransient_retriesvision)providerr  base_urlapi_keyr  
extra_bodydownload_timeoutweb_extractih  )r'  r  r(  r)  r  r*  
skills_hubapprovalmcptitle_generation)r'  r  r(  r)  r  r*  languagetts_audio_tagstriage_specifierkanban_decomposerprofile_describer<   r  iX  monitorbackground_reviewmoa_referencemoa_aggregatordisplaycompactpersonalityresume_displayfullresume_exchangesresume_max_user_charsresume_max_assistant_chars   resume_max_assistant_linesresume_skip_tool_onlybusy_input_mode	interruptbusy_steer_ack_enabled	interfaceclitui_auto_resume_recenttui_agents_nudgebell_on_completeshow_reasoningreasoning_fullmemory_notificationson	streaming
timestampsfinal_response_markdownr   persistent_outputpersistent_output_max_linespersist_promptsinline_diffsfile_mutation_verifiercredits_noticesturn_completion_explainer	show_costskindefaultr1  entui_status_indicatorkaomojicli_refresh_intervalg      ?user_message_preview)first_lines
last_linesinterim_assistant_messages
accumulatecode)telegramdiscord)r  context_pctr  )r  fieldsgQ?)r  slugrender_modescaleunicode_cols)tool_progress_commandtool_progress_overridestool_preview_lengthfriendly_tool_labelstool_progress_groupingstatus_phrasesreasoning_styleephemeral_system_ttl	platformsruntime_footercopy_shortcutpet	dashboard)	client_id
portal_url)usernamepassword_hashpasswordsecretsession_ttl_secondsdrain+   )scopemin_secret_chars)themeshow_token_analyticsoauth
basic_auth
drain_auth
public_urlprivacy
redact_piittsedgevoicezen-US-AriaNeuralpNInz6obpgDQGcFmaJgBeleven_multilingual_v2)voice_idmodel_idzgpt-4o-mini-ttsalloy)r  r  zgemini-2.5-flash-preview-ttsKore)r  r  
audio_tagspersona_prompt_fileevei]  i  )r  r1  sample_ratebit_ratezvoxtral-mini-tts-2603z$c69964a6-ab8b-4f8a-9465-ec0925096ec8)r  r  zneuphonic/neutts-air-q4-ggufcpu)	ref_audioref_textr  devicezen_US-lessac-medium)	r'  r  
elevenlabsopenaigeminixaimistralneuttspipersttbase)r  r1  z	whisper-1zvoxtral-mini-latest	scribe_v2)r  language_codetag_audio_eventsdiarize)r  echo_transcriptsr'  r  r  r  r  zctrl+bg      @)
record_keymax_recording_secondsauto_ttsbeep_enabledsilence_thresholdsilence_durationhuman_delayoffi   i	  )rf  min_msmax_mscontextr  
compressormemoryi  i_  )memory_enableduser_profile_enabledwrite_approvalmemory_char_limituser_char_limitr'  
delegation2   )r  r'  r(  r)  api_modeinherit_mcp_toolsetsmax_iterationsmax_summary_charschild_timeout_secondsreasoning_effortmax_concurrent_childrenmax_spawn_depthorchestrator_enabledsubagent_auto_approveprefill_messages_filegoalsmoazopenai-codexzgpt-5.5)r'  r  zdeepseek/deepseek-v4-prozanthropic/claude-opus-4.8i   )reference_models
aggregator
max_tokensr  )default_presetactive_presetsave_traces	trace_dirpresetsr  )external_dirstemplate_varsinline_shellinline_shell_timeoutguard_agent_createdr     )r  keep)r  interval_hoursmin_idle_hoursstale_after_daysarchive_after_daysconsolidateprune_builtinsbackuphonchotimezoneslack)require_mentionfree_response_channelsallowed_channelschannel_promptsrj  i   g
ףp=
?gQ?)zLet me look into that.zOne moment.zChecking on that now.zGive me a sec.zOn it.)r  ambient_enabledambient_pathambient_gain	duck_gainspeech_gainack_enabledack_phrases)r  r  r  auto_threadthread_require_mentionbots_require_inline_mentionhistory_backfillhistory_backfill_limit	reactionsr  dm_role_auth_guildserver_actionsallow_any_attachmentmax_attachment_bytesvoice_fxwhatsappri  )rich_messagesrich_drafts)r  r  allowed_chatsextra
mattermostmatrix)r  free_response_roomsallowed_rooms	approvalsmanualdeny)rf  r  	cron_moder  mcp_reload_confirmdestructive_slash_confirmcommand_allowlistquick_commandsplatform_hintsr  hooks_auto_acceptpersonalitiessecuritytirith)r  domainsshared_files)	r  redact_secretstirith_enabledtirith_pathtirith_timeouttirith_fail_openwebsite_blocklistacked_advisoriesallow_lazy_installsr{  zhttps://portal.nousresearch.com)r  callback_urlexpected_audiencenas_jwks_url)r'  chronoswrap_responsemirror_deliverymax_parallel_jobsoutput_retentioni    i@8  )dispatch_in_gatewaydispatch_interval_secondsfailure_limitworker_log_rotate_bytesworker_log_backup_countorchestrator_profiledefault_assigneemax_in_progress_per_profileauto_decomposeauto_decompose_per_tickdispatch_stale_timeout_secondscode_executionrf  projecttoolstool_search)r  threshold_pctsearch_default_limitmax_search_limitloggingINFO)levelmax_size_mbbackup_countmodel_catalogzAhttps://hermes-agent.nousresearch.com/docs/api/model-catalog.json)r  url	ttl_hoursr  network
force_ipv4gatewayidle_timeout_minutes)max_restartswindow_secondsr  i   max_concurrent_runs)
platform_connect_timeoutscale_to_zerorestart_loop_guardmessage_timestampsmax_inbound_media_bytesstrictmedia_delivery_allow_dirstrust_recent_filestrust_recent_files_seconds
api_serverg?u    ▉g        )r  	transportedit_intervalbuffer_thresholdcursorfresh_final_after_secondsr|  )r  r  vacuum_after_pruner  write_json_snapshots
onboardingask)seenprofile_buildupdatesstash)pre_update_backupbackup_keepnon_interactive_local_changesrefresh_cua_driverlspdocumentg      @)r  	wait_modewait_timeoutinstall_strategyserversx_searchzgrok-4.20-reasoning)r  timeout_secondsretries	bitwardenBWS_ACCESS_TOKEN)r  access_token_env
project_idcache_ttl_secondsoverride_existingauto_install
server_urlcua_telemetry)electron_flagsdisable_gpuglobal)rq  r!  !   )secretspaste_collapse_threshold!paste_collapse_threshold_fallbackpaste_collapse_char_thresholdcomputer_usedesktopvertex_config_version)FIRECRAWL_API_KEYBROWSERBASE_API_KEYBROWSERBASE_PROJECT_IDFAL_KEYVOICE_TOOLS_OPENAI_KEYELEVENLABS_API_KEY)r|   rj   WHATSAPP_ALLOWED_USERSSLACK_BOT_TOKENSLACK_APP_TOKENSLACK_ALLOWED_USERSTAVILY_API_KEYTERMINAL_MODAL_MODE)r     r  r     ENV_VARS_BY_VERSIONNOUS_BASE_URLzNous Portal base URL overridez.Nous Portal base URL (leave empty for default)r'  descriptionpromptrA  r  categoryadvancedOPENROUTER_API_KEYz>OpenRouter API key (for vision, web scraping helpers, and MoA)zOpenRouter API keyzhttps://openrouter.ai/keysvision_analyze)r  r  rA  r  r6  r  r  GOOGLE_API_KEYz<Google AI Studio API key (also recognized as GEMINI_API_KEY)zGoogle AI Studio API keyz&https://aistudio.google.com/app/apikeyGEMINI_API_KEYz3Google AI Studio API key (alias for GOOGLE_API_KEY)zGemini API keyGEMINI_BASE_URLz"Google AI Studio base URL overridez)Gemini base URL (leave empty for default)VERTEX_CREDENTIALS_PATHuI  Path to a Google Cloud service account JSON for Vertex AI (Gemini). Vertex uses OAuth2, not a static API key — this points at the credentials Hermes mints short-lived tokens from. Falls back to GOOGLE_APPLICATION_CREDENTIALS, then to ADC (gcloud auth application-default login). Set project/region under vertex: in config.yaml.zZVertex service account JSON path (leave empty to use ADC / GOOGLE_APPLICATION_CREDENTIALS)z4https://cloud.google.com/iam/docs/keys-create-deleteXAI_API_KEYzxAI API keyzhttps://console.x.ai/XAI_BASE_URLzxAI base URL overridez&xAI base URL (leave empty for default)NVIDIA_API_KEYz;NVIDIA NIM API key (build.nvidia.com or local NIM endpoint)zNVIDIA NIM API keyzhttps://build.nvidia.com/NVIDIA_BASE_URLzJNVIDIA NIM base URL override (e.g. http://localhost:8000/v1 for local NIM)z-NVIDIA NIM base URL (leave empty for default)
LM_API_KEYz5LM Studio bearer token for auth-enabled local serversz LM Studio API key / bearer tokenLM_BASE_URLzLM Studio base URL overridez,LM Studio base URL (leave empty for default)GLM_API_KEYzBZ.AI / GLM API key (also recognized as ZAI_API_KEY / Z_AI_API_KEY)zZ.AI / GLM API keyzhttps://z.ai/ZAI_API_KEYz$Z.AI API key (alias for GLM_API_KEY)zZ.AI API keyZ_AI_API_KEYGLM_BASE_URLzZ.AI / GLM base URL overridez-Z.AI / GLM base URL (leave empty for default)KIMI_API_KEYzKimi / Moonshot API keyzKimi API keyzhttps://platform.moonshot.cn/KIMI_BASE_URLz!Kimi / Moonshot base URL overridez'Kimi base URL (leave empty for default)KIMI_CN_API_KEYzKimi / Moonshot China API keyzKimi (China) API keySTEPFUN_API_KEYzStepFun Step Plan API keyzhttps://platform.stepfun.com/STEPFUN_BASE_URLz#StepFun Step Plan base URL overridez4StepFun Step Plan base URL (leave empty for default)ARCEEAI_API_KEYzArcee AI API keyzhttps://chat.arcee.ai/ARCEE_BASE_URLzArcee AI base URL overridez(Arcee base URL (leave empty for default)GMI_API_KEYzGMI Cloud API keyzhttps://www.gmicloud.ai/GMI_BASE_URLzGMI Cloud base URL overridez,GMI Cloud base URL (leave empty for default)MINIMAX_API_KEYzMiniMax API key (international)zMiniMax API keyzhttps://www.minimax.io/MINIMAX_BASE_URLzMiniMax base URL overridez*MiniMax base URL (leave empty for default)MINIMAX_CN_API_KEYz MiniMax API key (China endpoint)zMiniMax (China) API keyzhttps://www.minimaxi.com/MINIMAX_CN_BASE_URLz!MiniMax (China) base URL overridez2MiniMax (China) base URL (leave empty for default)DEEPSEEK_API_KEYz+DeepSeek API key for direct DeepSeek accesszDeepSeek API Keyz&https://platform.deepseek.com/api_keysr  r  rA  r  r  DEEPSEEK_BASE_URLz'Custom DeepSeek API base URL (advanced)zDeepSeek Base URLDASHSCOPE_API_KEYz>Alibaba Cloud DashScope API key (Qwen + multi-provider models)zDashScope API Keyz-https://modelstudio.console.alibabacloud.com/DASHSCOPE_BASE_URLzGCustom DashScope base URL (default: coding-intl OpenAI-compat endpoint)zDashScope Base URLHERMES_QWEN_BASE_URLzBQwen Portal base URL override (default: https://portal.qwen.ai/v1)z.Qwen Portal base URL (leave empty for default)OPENCODE_ZEN_API_KEYz=OpenCode Zen API key (pay-as-you-go access to curated models)zOpenCode Zen API keyzhttps://opencode.ai/authOPENCODE_ZEN_BASE_URLzOpenCode Zen base URL overridez/OpenCode Zen base URL (leave empty for default)OPENCODE_GO_API_KEYz<OpenCode Go API key ($10/month subscription for open models)zOpenCode Go API keyOPENCODE_GO_BASE_URLzOpenCode Go base URL overridez.OpenCode Go base URL (leave empty for default)HF_TOKENzVHugging Face token for Inference Providers (20+ open models via router.huggingface.co)zHugging Face Tokenz&https://huggingface.co/settings/tokensHF_BASE_URLz2Hugging Face Inference Providers base URL overridez%HF base URL (leave empty for default)OLLAMA_API_KEYu>   Ollama Cloud API key (ollama.com — cloud-hosted open models)zOllama Cloud API keyzhttps://ollama.com/settingsOLLAMA_BASE_URLz?Ollama Cloud base URL override (default: https://ollama.com/v1)z)Ollama base URL (leave empty for default)XIAOMI_API_KEYzhXiaomi MiMo API key for MiMo models (mimo-v2.5-pro, mimo-v2.5, mimo-v2-pro, mimo-v2-omni, mimo-v2-flash)zXiaomi MiMo API Keyzhttps://platform.xiaomimimo.comXIAOMI_BASE_URLzFXiaomi MiMo base URL override (default: https://api.xiaomimimo.com/v1)z)Xiaomi base URL (leave empty for default)
AWS_REGIONz?AWS region for Bedrock API calls (e.g. us-east-1, eu-central-1)z
AWS RegionzIhttps://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-regions.htmlAWS_PROFILEzFAWS named profile for Bedrock authentication (from ~/.aws/credentials)zAWS ProfileAZURE_FOUNDRY_API_KEYz0Azure Foundry API key for custom Azure endpointszAzure Foundry API Keyzhttps://ai.azure.com/AZURE_FOUNDRY_BASE_URLzLAzure Foundry base URL (set via 'hermes model' for endpoint-specific config)zAzure Foundry base URLEXA_API_KEYz1Exa API key for AI-native web search and contentszExa API keyzhttps://exa.ai/
web_searchtool)r  r  rA  r6  r  r  PARALLEL_API_KEYz5Parallel API key for AI-native web search and extractzParallel API keyzhttps://parallel.ai/r  z-Firecrawl API key for web search and scrapingzFirecrawl API keyzhttps://firecrawl.dev/FIRECRAWL_API_URLz6Firecrawl API URL for self-hosted instances (optional)z)Firecrawl API URL (leave empty for cloud)FIRECRAWL_GATEWAY_URLzQExact Firecrawl tool-gateway origin override for Nous Subscribers only (optional)z9Firecrawl gateway URL (leave empty to derive from domain)TOOL_GATEWAY_DOMAINzShared tool-gateway domain suffix for Nous Subscribers only, used to derive vendor hosts, e.g. nousresearch.com -> firecrawl-gateway.nousresearch.comzTool-gateway domain suffixTOOL_GATEWAY_SCHEMEzShared tool-gateway URL scheme for Nous Subscribers only, used to derive vendor hosts (`https` by default, set `http` for local gateway testing)zTool-gateway URL schemeTOOL_GATEWAY_USER_TOKENzuExplicit Nous Subscriber access token for tool-gateway requests (optional; otherwise read from the Hermes auth store)zTool-gateway user tokenz3Tavily API key for AI-native web search and extractzTavily API keyzhttps://app.tavily.com/homeSEARXNG_URLz<URL of your SearXNG instance for free self-hosted web searchz(SearXNG URL (e.g. http://localhost:8080)z"https://searxng.github.io/searxng/BRAVE_SEARCH_API_KEYzABrave Search API subscription token (free tier: 2,000 queries/mo)zBrave Search subscription tokenzhttps://brave.com/search/api/r  uU   Browserbase API key for cloud browser (optional — local browser works without this)zBrowserbase API keyzhttps://browserbase.com/browser_navigatebrowser_clickr  uC   Browserbase project ID (optional — only needed for cloud browser)zBrowserbase project IDBROWSER_USE_API_KEYuU   Browser Use API key for cloud browser (optional — local browser works without this)zBrowser Use API keyzhttps://browser-use.com/FIRECRAWL_BROWSER_TTLz@Firecrawl browser session TTL in seconds (optional, default 300)zBrowser session TTL (seconds))r  r  r6  r  r  AGENT_BROWSER_ENGINEzaBrowser engine for local mode: auto (default Chrome), lightpanda (faster, no screenshots), chromez'Browser engine (auto/lightpanda/chrome)z,https://github.com/vercel-labs/agent-browser)r  browser_snapshotr  browser_vision)r  r  rA  r6  r  r  r  CAMOFOX_URLzYCamofox browser server URL for local anti-detection browsing (e.g. http://localhost:9377)zCamofox server URLz)https://github.com/jo-inc/camofox-browserCAMOFOX_API_KEYz[Optional bearer token sent as Authorization header to a remote/authenticated Camofox serverzCamofox API keyr  z*FAL API key for image and video generationzFAL API keyzhttps://fal.ai/image_generatevideo_generateKREA_API_KEYz9Krea API key for Krea 2 image generation (Medium + Large)zKrea API keyz'https://www.krea.ai/settings/api-tokensz?OpenAI API key for voice transcription (Whisper) and OpenAI TTSz&OpenAI API Key (for Whisper STT + TTS)z$https://platform.openai.com/api-keysvoice_transcription
openai_ttszMElevenLabs API key for premium text-to-speech voices and Scribe transcriptionzElevenLabs API keyzhttps://elevenlabs.io/elevenlabs_ttsMISTRAL_API_KEYz7Mistral API key for Voxtral TTS and transcription (STT)zMistral API keyzhttps://console.mistral.ai/GITHUB_TOKENzCGitHub token for Skills Hub (higher API rate limits, skill publish)zGitHub Tokenz"https://github.com/settings/tokensNOTION_API_KEYz5Notion integration token (used by the `notion` skill)zNotion API keyz%https://www.notion.so/my-integrationsskillLINEAR_API_KEYz4Linear personal API key (used by the `linear` skill)zLinear API keyz,https://linear.app/settings/account/securityAIRTABLE_API_KEYz=Airtable personal access token (used by the `airtable` skill)zAirtable API keyz"https://airtable.com/create/tokensTENOR_API_KEYz=Tenor API key for GIF search (used by the `gif-search` skill)zTenor API keyz5https://developers.google.com/tenor/guides/quickstartHONCHO_API_KEYz.Honcho API key for AI-native persistent memoryzHoncho API keyzhttps://app.honcho.devhoncho_contextHONCHO_BASE_URLz=Base URL for self-hosted Honcho instances (no API key needed)z,Honcho base URL (e.g. http://localhost:8000))r  r  r  HINDSIGHT_API_KEYz3Hindsight API key for graph-aware persistent memoryzHindsight API keyzhttps://hindsight.vectorize.iohindsight_recallHINDSIGHT_API_URLzLBase URL for the Hindsight API (default: https://api.hindsight.vectorize.io)zHindsight API URL)r  r  r  r  SUPERMEMORY_API_KEYz=Supermemory API key for conversation-scoped persistent memoryzSupermemory API keyzhttps://supermemory.aisupermemory_searchMEM0_API_KEYz4Mem0 Platform API key for semantic persistent memoryzMem0 API keyzhttps://app.mem0.aimem0_searchRETAINDB_API_KEYz&RetainDB API key for persistent memoryzRetainDB API keyzhttps://retaindb.comretaindb_searchRETAINDB_BASE_URLzOBase URL for self-hosted RetainDB instances (default: https://api.retaindb.com)zRetainDB base URLBRV_API_KEYuG   ByteRover API key (optional, for cloud sync — local-first by default)zByteRover API keyzhttps://app.byterover.dev	brv_queryOPENVIKING_API_KEYz3OpenViking API key (leave blank for local dev mode)zOpenViking API keyviking_searchOPENVIKING_ENDPOINTz6OpenViking server URL (default: http://127.0.0.1:1933)zOpenViking endpointHERMES_LANGFUSE_PUBLIC_KEYz'Langfuse project public key (pk-lf-...)zLangfuse public keyzhttps://cloud.langfuse.comHERMES_LANGFUSE_SECRET_KEYz'Langfuse project secret key (sk-lf-...)zLangfuse secret keyHERMES_LANGFUSE_BASE_URLz9Langfuse server URL (default: https://cloud.langfuse.com)z8Langfuse server URL (leave empty for cloud.langfuse.com)TELEGRAM_BOT_TOKENz"Telegram bot token from @BotFatherzTelegram bot tokenzhttps://t.me/BotFather	messagingTELEGRAM_ALLOWED_USERSzSComma-separated Telegram user IDs allowed to use the bot (get ID from @userinfobot)z+Allowed Telegram user IDs (comma-separated)zhttps://t.me/userinfobotTELEGRAM_PROXYzaProxy URL for Telegram connections (overrides HTTPS_PROXY). Supports http://, https://, socks5://zTelegram proxy URL (optional))r  r  r  r  DISCORD_BOT_TOKENz'Discord bot token from Developer PortalzDiscord bot tokenz+https://discord.com/developers/applicationsDISCORD_ALLOWED_USERSz7Comma-separated Discord user IDs allowed to use the botz*Allowed Discord user IDs (comma-separated)DISCORD_REPLY_TO_MODEzDiscord reply threading mode: 'off' (no reply references), 'first' (reply on first message only, default), 'all' (reply on every chunk)z"Discord reply mode (off/first/all)r  zSlack bot token (xoxb-). Get from OAuth & Permissions after installing your app. Required scopes: chat:write, app_mentions:read, channels:history, groups:history, im:history, im:read, im:write, mpim:history, mpim:read, users:read, files:read, files:writezSlack Bot Token (xoxb-...)zIn your Slack app, add the required bot scopes, install the app to the workspace, then copy OAuth & Permissions > Bot User OAuth Token.zhttps://api.slack.com/apps)r  r  helprA  r  r  r  u   Slack app-level token (xapp-) for Socket Mode. Get from Basic Information → App-Level Tokens. Also ensure Event Subscriptions include: message.im, message.channels, message.groups, message.mpim, app_mentionzSlack App Token (xapp-...)zyIn your Slack app, enable Socket Mode, then create Basic Information > App-Level Tokens with the connections:write scope.r  zComma-separated Slack member IDs allowed to use Hermes, e.g. U01ABC2DEF3. Without this, Slack may connect but deny messages by default.zAllowed Slack member IDszvIn Slack, open your profile, choose More or the three-dot menu, then Copy member ID. Add multiple IDs comma-separated.MATTERMOST_URLz3Mattermost server URL (e.g. https://mm.example.com)zMattermost server URLzhttps://mattermost.com/deploy/MATTERMOST_TOKENz-Mattermost bot token or personal access tokenzMattermost bot tokenMATTERMOST_ALLOWED_USERSz:Comma-separated Mattermost user IDs allowed to use the botz-Allowed Mattermost user IDs (comma-separated)MATTERMOST_REQUIRE_MENTIONzaRequire @mention in Mattermost channels (default: true). Set to false to respond to all messages.zRequire @mention in channels!MATTERMOST_FREE_RESPONSE_CHANNELSzJComma-separated Mattermost channel IDs where bot responds without @mentionz+Free-response channel IDs (comma-separated)MATRIX_HOMESERVERz7Matrix homeserver URL (e.g. https://matrix.example.org)zMatrix homeserver URLz%https://matrix.org/ecosystem/servers/MATRIX_ACCESS_TOKENz3Matrix access token (preferred over password login)zMatrix access tokenMATRIX_USER_IDz)Matrix user ID (e.g. @hermes:example.org)zMatrix user ID (@user:server)MATRIX_ALLOWED_USERSzLComma-separated Matrix user IDs allowed to use the bot (@user:server format)z)Allowed Matrix user IDs (comma-separated)r   zZRequire @mention in Matrix rooms (default: true). Set to false to respond to all messages.z&Require @mention in rooms (true/false)r   zCComma-separated Matrix room IDs where bot responds without @mentionz(Free-response room IDs (comma-separated)r   z@Auto-create threads for messages in Matrix rooms (default: true)z)Auto-create threads in rooms (true/false)r   z>Auto-create threads for DM messages in Matrix (default: false)z'Auto-create threads in DMs (true/false)ru   zNStable Matrix device ID for E2EE persistence across restarts (e.g. HERMES_BOT)z)Matrix device ID (stable across restarts)r   u   Matrix recovery key for cross-signing verification after device key rotation (from Element: Settings → Security → Recovery Key)zMatrix recovery keyr   zOBlueBubbles server URL for iMessage integration (e.g. http://192.168.1.10:1234)zBlueBubbles server URLzhttps://bluebubbles.app/r   uJ   BlueBubbles server password (from BlueBubbles Server → Settings → API)zBlueBubbles server passwordBLUEBUBBLES_ALLOWED_USERSzJComma-separated iMessage addresses (email or phone) allowed to use the botz,Allowed iMessage addresses (comma-separated)BLUEBUBBLES_ALLOW_ALL_USERSz-Allow all BlueBubbles users without allowlistzAllow All BlueBubbles Usersr_   z.QQ Bot App ID from QQ Open Platform (q.qq.com)z	QQ App IDzhttps://q.qq.com)r  r  rA  r  rx   z*QQ Bot Client Secret from QQ Open PlatformzQQ Client Secretrw   z2Comma-separated QQ user IDs allowed to use the botzQQ Allowed Usersr   z=Comma-separated QQ group IDs allowed to interact with the botzQQ Group Allowed Usersr   z4Allow all QQ users without an allowlist (true/false)zAllow All QQ Usersr   z<Default QQ channel/group for cron delivery and notificationszQQ Home Channelr   z$Display name for the QQ home channelzQQ Home Channel Name
QQ_SANDBOXz;Enable QQ sandbox mode for development testing (true/false)zQQ Sandbox Moder`   z*IRC server hostname (e.g. irc.libera.chat)z
IRC serverra   z"IRC channel to join (e.g. #hermes)zIRC channelrc   z)Bot nickname on IRC (default: hermes-bot)zIRC nicknamer   z!IRC server password (if required)zIRC server passwordr   z)NickServ password for nick identificationzNickServ passwordGATEWAY_ALLOW_ALL_USERSzMAllow all users to interact with messaging bots (true/false). Default: false.zAllow all users (true/false)API_SERVER_ENABLEDzrEnable the OpenAI-compatible API server (true/false). Allows frontends like Open WebUI, LobeChat, etc. to connect.zEnable API server (true/false)API_SERVER_KEYz|Bearer token for API server authentication. Required whenever the API server is enabled; server refuses to start without it.zAPI server auth keyAPI_SERVER_PORTz(Port for the API server (default: 8642).zAPI server portAPI_SERVER_HOSTzsHost/bind address for the API server (default: 127.0.0.1). API_SERVER_KEY is still required even on loopback binds.zAPI server hostzModel name advertised on /v1/models. Defaults to the profile name (or 'hermes-agent' for the default profile). Useful for multi-user setups with OpenWebUI.zAPI server model nameu  URL of a remote Hermes API server to forward messages to (proxy mode). When set, the gateway handles platform I/O only — all agent work is delegated to the remote server. Use for Docker E2EE containers that relay to a host agent. Also configurable via gateway.proxy_url in config.yaml.z=Remote Hermes API server URL (e.g. http://192.168.1.100:8642)zBearer token for authenticating with the remote Hermes API server (proxy mode). Must match the API_SERVER_KEY on the remote host.zRemote API server auth keyzREnable the webhook platform adapter for receiving events from GitHub, GitLab, etc.zEnable webhooks (true/false)z1Port for the webhook HTTP server (default: 8644).zWebhook portz[Global HMAC secret for webhook signature validation (overridable per route in config.yaml).zWebhook secretzySudo password for terminal commands requiring root access; set to an explicit empty string to try empty without promptingzSudo passwordsettingzFPath to JSON file with ephemeral prefill messages for few-shot primingzPrefill messages file pathzOEphemeral system prompt injected at API-call time (never persisted to sessions)zEphemeral system prompt)	API_SERVER_MODEL_NAMEGATEWAY_PROXY_URLGATEWAY_PROXY_KEYWEBHOOK_ENABLEDWEBHOOK_PORTWEBHOOK_SECRETSUDO_PASSWORDHERMES_PREFILL_MESSAGES_FILEHERMES_EPHEMERAL_SYSTEM_PROMPTrequired_onlyc                 :   g }t                                           D ]1\  }}t          |          s|                    d|i|ddi           2| sKt                                          D ]1\  }}t          |          s|                    d|i|ddi           2|S )z|
    Check which environment variables are missing.
    
    Returns list of dicts with var info for missing variables.
    r   is_requiredTF)REQUIRED_ENV_VARSitemsget_env_valueappendOPTIONAL_ENV_VARS)r"  missingvar_namer>  s       r$   get_missing_env_varsr,    s     G ,1133 L L$X&& 	LNNFHJJmTJJKKK  Q/5577 	Q 	QNHd ** QODO-OOPPPNrZ   
dotted_keyc           	      j   |                     d          }| }|dd         D ]}t          |t                    rE	 t          |          }n+# t          t
          f$ r t	          d|d|d          w xY w||         }\t          |t                    rC|                    |          }||vst          |t          t          f          si ||<   ||         }t	          dt          |          j	         d|          |d         }t          |t                    r||t          |          <   dS |||<   dS )	u  Set a value at an arbitrarily nested dotted key path.

    Supports both dict and list navigation:
      _set_nested(c, "a.b.c", 1)     → c["a"]["b"]["c"] = 1
      _set_nested(c, "a.0.b", 1)     → c["a"][0]["b"] = 1
      _set_nested(c, "providers.1", "x") → c["providers"][1] = "x"

    Intermediate dicts are created on demand.  List indices are parsed
    from numeric path segments; the referenced index must already exist
    (we do not grow lists — the user is navigating into structure they
    wrote themselves).  If a segment targets a non-container leaf
    (scalar), the leaf is replaced with a fresh dict so the write can
    proceed — this preserves the pre-existing behavior for bare scalar
    overrides (e.g. setting ``a.b.c`` where ``a.b`` was previously a
    string).

    Guards against #17876: before this fix the code unconditionally
    replaced any non-dict value (including lists) with ``{}``, silently
    destroying list-typed config like ``custom_providers`` whenever a
    caller used an indexed path.
    r)   Nr\  z!Cannot navigate into list at key z
: segment z is not a numeric indexzCannot navigate into z at key )
split
isinstancer   rU  	TypeErrorrX   dictr   type__name__)	configr-  rB  partscurrentpartidxr#   lasts	            r$   _set_nestedr;    s|   , S!!EGcrc
  gt$$ 	$iiz*   ?
 ? ?#? ? ?  
 clGG&& 		{{4((H7""*Xd|*L*L" "dmGGVW(>VV
VV   9D'4   "D		s   A

(A2)clear_api_keyclear_api_modeclear_base_url	model_cfgr<  r=  r>  c                    t          | t                    s| S |r,|                     dd           |                     dd           |r|                     dd           |r|                     dd           | S )a  Remove stale inline endpoint credentials from a model config.

    ``model.api_key`` is valid only for explicit custom endpoint assignments.
    Built-in providers resolve credentials from env vars, auth.json, or the
    credential pool. When switching away from a custom endpoint, leaving these
    fields behind keeps secrets in config.yaml and can contaminate later custom
    resolution paths.
    r)  Napir  r(  )r0  r2  pop)r?  r<  r=  r>  s       r$    clear_model_endpoint_credentialsrC  8  s     i&&  #i&&&eT""" (j$''' (j$'''rZ   c                      t                      } g ddt          dt          dt          ffd t          |            S )z
    Check which config fields are missing or outdated (recursive).
    
    Walks the DEFAULT_CONFIG tree at arbitrary depth and reports any keys
    present in defaults but absent from the user's loaded config.
    r   defaultsr7  r  c                 `   |                                  D ]\  }}|                    d          r|s|n| d| }||vr                    ||d| d           Gt          |t                    r;t          |                    |          t                    r |||         |           d S )NrA  r)   zNew config option: )r4   r^  r  )r&  r:  r(  r0  r2  r   )rE  r7  r  r4   default_valuefull_key_checkr*  s         r$   rI  z)get_missing_config_fields.<locals>._check]  s    "*.."2"2 	> 	>C~~c"" "(?ss.?.?#.?.?H'!!#,#C#C#C       
 M400 >ZC@P@PRV5W5W >}gclH===	> 	>rZ   )r   )load_configr2  r+   DEFAULT_CONFIG)r5  rI  r*  s    @@r$   get_missing_config_fieldsrL  S  sj     ]]FG> > > >c > > > > > > > F>6"""NrZ   c                  6   	 ddl m} m} n# t          $ r g cY S w xY w	  |             }nF# t          $ r9}ddl} |j        t                                        d|           g cY d}~S d}~ww xY w|sg S t                      }g }|D ]}| d|d          }|	                    d          }	|}
d}|	D ](}t          |
t                    r||
v r|
|         }
|
}&d} |)t          |t                    r)|                                s|                    |           |S )a3  Return skill-declared config vars that are missing or empty in config.yaml.

    Scans all enabled skills for ``metadata.hermes.config`` entries, then checks
    which ones are absent or empty under ``skills.config.<key>`` in the user's
    config.yaml.  Returns a list of dicts suitable for prompting.
    r   )discover_all_skill_config_varsSKILL_CONFIG_PREFIXNz)discover_all_skill_config_vars failed: %sr)   r4   )agent.skill_utilsrN  rO  r   r;  	getLoggerr4  debugrJ  r/  r0  r2  r+   r   r(  )rN  rO  all_varser;  r5  r*  varstorage_keyr6  r7  rB  r8  s                r$   get_missing_skill_config_varsrW  o  s   YYYYYYYYY   			
1133    	(##))7	
 	
 	
 						  	]]F$&G    ,;;s5z;;!!#&& 	 	D'4(( TW__!$-=Zs33=EKKMM=NN3Ns&    
) 
A,.A'!A,'A,_PROVIDER_NORMALIZE_WARNEDprovider_key	signaturer5   argsc                     | pd|f}|t           v rdS t                               |           t          j        |g|R   dS )zKEmit ``logger.warning(msg, *args)`` at most once per (provider, signature).?N)rX  r-   r.   r/   )rY  rZ  r5   r[  	dedup_keys        r$   _warn_once_per_providerr_    sV     $i0I...""9---
N3rZ   rY  entryc                P   t          | t                    sdS ddddddddd	}d
| v rd| vr| d
         | d<   h d}|                                D ]1\  }}|| v r(|| vr$t          |d| d|pd||           | |         | |<   2t	          |                                           |z
  t	          |                                          z
  }|rXt          |dd                    t          |                    z   d|pdd                    t          |                               ddlm	} d}dD ]}	| 
                    |	          }
t          |
t                    r}|
                                ri|
                                }t          j        d|          r|} n= ||          }|j        r|j        r|} n t"                              d|pd|	|           |sdS d}| 
                    d          }t          |t                    r)|                                r|                                }n(|                                r|                                }|sdS ||d}|                                }|r||d<   | 
                    d          }t          |t                    r+|                                r|                                |d<   | 
                    d          }t          |t                    r+|                                r|                                |d<   | 
                    d          p| 
                    d          }t          |t                    r+|                                r|                                |d<   | 
                    d          p| 
                    d          }t          |t                    r+|                                r|                                |d<   | 
                    d          }t          |t                    r	|r||d<   n2t          |t&                    r|ri }|D ]}t          |t                    r,|                                ri ||                                <   Dt          |t                    sZ|
                    d          }t          |t                    r|                                s|
                    d          }t          |t                    r|                                sd  |                                D             }|||                                <   |r||d<   | 
                    d          }t          |t(                    r|dk    r||d<   | 
                    d          }t          |t(          t*          f          r|dk    r||d<   | 
                    d!          }t          |t,                    r||d!<   | 
                    d"          }t          |t                    rt          |          |d"<   t/          | 
                    d#                    }|r||d#<   | 
                    d$          }t          |t                    r+|                                r|                                |d$<   | 
                    d%          }t          |t,                    r||d%<   n@t          |t                    r+|                                r|                                |d%<   |S )&z>Return a runtime-compatible custom provider entry or ``None``.Nr)  r(  r  key_envdefault_modelcontext_lengthrate_limit_delay)apiKeybaseUrlapiModekeyEnv	apiKeyEnvdefaultModelcontextLengthrateLimitDelayapi_key_env>   rA  rA  r   r  modelsr)  rc  r  r(  r'  rT  r*  
ssl_verifyro  ssl_ca_certrd  extra_headersre  discover_modelsrf  stale_timeout_secondsrequest_timeout_secondszcamel:z[providers.%s: camelCase key '%s' auto-mapped to '%s' (use snake_case to avoid this warning)r]  zunknown:,z-providers.%s: unknown config keys ignored: %s, r   urlparser   )r(  rA  rA  z	\{[^}]+\}uP   providers.%s: '%s' value '%s' is not a valid URL (no scheme or host) — skippedr   )r   r(  rY  rT  r  rp  idc                 "    i | ]\  }}|d v	||S )>   r{  r   r   .0kvs      r$   
<dictcomp>z4_normalize_custom_provider_entry.<locals>.<dictcomp><  s/       A.1H1H11H1H1HrZ   rt  r*  rs  rr  rq  )r0  r2  r&  r_  setkeysjoinsortedurllib.parserz  r   r+   r   researchschemenetlocr.   r/   r   rU  floatr  normalize_extra_headers) ra  rY  _CAMEL_ALIASES_KNOWN_KEYScamelsnakeunknownrz  r(  url_keyraw_url	candidateparsedr   raw_namer   r)  rc  r  
model_namerp  normalized_modelsitemr  
model_metare  rf  rt  r*  normalized_headersrr  rq  s                                    r$    _normalize_custom_provider_entryr    s    eT"" t '),	& 	&N )5"8"8 /i  K ',,.. ( (uE>>e500#.u..9#UE	   !<E%L%**,,+-N4G4G4I4I0J0JJG 
*sxxw'@'@@;C6'??!;!;	
 	
 	
 &%%%%%H-  ))G$$gs## 	 	I yy11 $Xi((F}  $6 'C)  
  tDyy  H(C   $X^^%5%5 $~~					 $!!## t " "J
  %%''L 2%1
>"ii	""G'3 0GMMOO 0 '
9ii	""G'3 0GMMOO 0 '
9yy$$>		+(>(>H(C   2X^^%5%5 2!)!1!1
:7##Auyy'A'AJ*c"" 1z'7'7'9'9 1(..00
7YYx  F&$ 5F 5%
8	FD	!	! 5f 5 -/ 	= 	=D$$$  24!$**,,/dD)) xx~~Hh,, ,HNN4D4D ,88F++h,, HNN4D4D  !%  J 3=hnn..// 	5#4Jx YY/00N.#&& 6>A+=+='5
#$yy!344"S%L11 :6F!6K6K)9
%&ii 122O/4(( 8(7
$%<((J*d## 4#'
#3#3
< 
 1?1K1KLL 9&8
?#))M**K+s## 8(9(9(;(; 8$/$5$5$7$7
=!<((J*d## 6#-
<  	J	$	$ 6)9)9);); 6#-#3#3#5#5
< rZ   c                    t          t          | t                    rt          |           n| |          }|dS d|d         i}dD ]}||v r||         ||<   d|v r|d         |d<   d|v r|d         |d	<   |S )
zDTranslate a legacy custom provider entry to the v12 providers shape.r`  NrA  r(  )r   r)  rc  rp  re  rf  rt  r*  rs  rr  rq  r  rd  r  rT  )r  r0  r2  )ra  rY  r   provider_entryfields        r$   )_custom_provider_entry_to_provider_configr  g  s     2!%..9UE!  J t&+Z
-C%DN 6 6 J$.u$5N5!**4W*='Z&0&<{#rZ   providers_dictc                     t          | t                    sg S g }|                                 D ]:\  }}t          |t	          |                    }||                    |           ;|S )zMNormalize ``providers`` config entries into the legacy custom-provider shape.r`  )r0  r2  r&  r  r+   r(  )r  custom_providersr4   ra  r   s        r$   "providers_dict_to_custom_providersr    sw    nd++ 	-/$**,, 0 0
U5e#c((SSS
!##J///rZ   r5  c                    | t                      } g t                      t                      dt          t          t          t
          f                  ddffd}|                     d          }|4t          |t                    sg S |D ]} |t          |                     t          |                     d                    D ]} ||           S )ay  Return a deduplicated custom-provider view across legacy and v12+ config.

    ``custom_providers`` remains the on-disk legacy format, while ``providers``
    is the newer keyed schema.  Runtime and picker flows still need a single
    list-shaped view, but we should not materialise that compatibility layer
    back into config.yaml because it duplicates entries in UIs.
    Nra  r   c                 >   | d S t          |                     dd          pd                                                                          }t          |                     dd          pd                                                                          }t          |                     dd          pd                                                              d                                          }t          |                     dd          pd                                                                          }|||f}|r|v rd S |r|r|v rd S                     |            |r                    |           |r|r                    |           d S d S d S )NrY  r   r   r(  r  r  )r+   r   r   r   rstripr(  r-   )	ra  rY  r   r(  r  pair
compatibleseen_name_url_pairsseen_provider_keyss	         r$   _append_if_newz7get_compatible_custom_providers.<locals>._append_if_new  s   =F599^R88>B??EEGGMMOO599VR((.B//5577==??uyyR006B77==??FFsKKQQSSEIIgr**0b117799??AAh& 	L,>>>F 	H 	)<!<!<F%    	1""<000 	*H 	*##D)))))	* 	* 	* 	*rZ   r  r  )rJ  r  r   r   r+   r   r   r0  r   r  r  )r5  r  r  ra  r  r  r  s       @@@r$   get_compatible_custom_providersr    s    ~')J!ee"uu*htCH~6 *4 * * * * * * * *( zz"455#*D11 	I% 	D 	DEN;EBBCCCC3FJJ{4K4KLL  urZ   rB  c                     | d S t          | t                    r| S t          | t                    r2|                                                                 }|dv rdS |dv rdS d S )N>   0nor  falseF>   r   rQ  r   r   T)r0  r  r+   r   r   )rB  lowereds     r$   _coerce_ssl_verifyr    su    }t% % ++--%%''111500044rZ   r(  r  c                    |#	 t          |          }n# t          $ r g }Y nw xY w| rt          |t                    si S | pd                    d                                          }|D ]}t          |t                    s|                    d          pd                    d                                          }|r||k    r]i }|                    d          }t          |t                    r+|	                                r|	                                |d<   t          |                    d                    }|||d<   |c S i S )zOReturn TLS settings from a matching ``custom_providers`` / ``providers`` entry.Nr   r  r(  rr  rq  )r  r   r0  r   r  r   r2  r   r+   r   r  )	r(  r  r5  
target_urlra  	entry_urloutcaverifys	            r$    get_custom_provider_tls_settingsr    st    	">vFF 	" 	" 	"!	" :&6== 	 .b((--3355J!  %&& 	YYz**0b88==CCEE	 	I33 YY}%%b# 	,288:: 	,!#C#EIIl$;$;<< &C


I    ##client_kwargsc                     t          |||          }|                    d          r|d         | d<   d|v r|d         | d<   dS dS )zCAttach per-provider TLS knobs to OpenAI client kwargs when matched.rr  rq  N)r  r   )r  r(  r  r5  tlss        r$   *apply_custom_provider_tls_to_client_kwargsr    sa     +85Ev
N
NC
ww} :'*='9m$s&),&7l### rZ   rs  c                 p    t          | t                    r| si S d |                                 D             S )a.  Normalize a raw ``extra_headers`` value into a ``dict[str, str]``.

    Stringifies keys and values and drops entries whose value is ``None``.
    Returns ``{}`` for non-dict or empty inputs. This is the single shared
    normalizer for per-provider ``extra_headers`` across config normalization,
    runtime resolution, client construction, and live ``/models`` discovery.

    SECURITY: header values routinely carry credentials (Cloudflare Access
    service tokens, proxy auth, custom bearer schemes). Callers must never
    log the returned values.
    c                 R    i | ]$\  }}|t          |          t          |          %S N)r+   r}  s      r$   r  z+normalize_extra_headers.<locals>.<dictcomp>  s+    NNNtq!CFFCFFrZ   )r0  r2  r&  )rs  s    r$   r  r    sB     mT** - 	NN}':':'<'<NNNNrZ   c                    |#	 t          |          }n# t          $ r g }Y nw xY w| rt          |t                    si S | pd                    d                                          }|D ]}t          |t                    s|                    d          pd                    d                                          }|r||k    r]t          |                    d                    c S i S )a  Return ``extra_headers`` from a matching ``providers`` / ``custom_providers`` entry.

    Matches the entry whose ``base_url`` equals *base_url* (trailing-slash and
    case insensitive, mirroring :func:`get_custom_provider_tls_settings`) and
    returns its ``extra_headers`` dict, or ``{}`` when no entry matches or the
    entry declares none.

    SECURITY: header values routinely carry credentials (Cloudflare Access
    service tokens, proxy auth, custom bearer schemes). Callers must never
    log the returned values.
    Nr   r  r(  rs  )	r  r   r0  r   r  r   r2  r   r  )r(  r  r5  r  ra  r  s         r$   !get_custom_provider_extra_headersr     s     	">vFF 	" 	" 	"!	" :&6== 	.b((--3355J! C C%&& 	YYz**0b88==CCEE	 	I33&uyy'A'ABBBBBIr  c                     t          |||          }|sdS t          |                     d          pi           }|                    |           || d<   dS )u  Merge per-provider ``extra_headers`` onto OpenAI client ``default_headers``.

    Provider-specific headers win over provider/SDK defaults already present in
    ``client_kwargs`` — they are the most specific configuration level. No-op
    when the base_url matches no ``providers`` / ``custom_providers`` entry or
    the entry declares no headers.

    SECURITY: values may carry credentials — never log them.
    Ndefault_headers)r  r2  r   update)r  r(  r  r5  rs  mergeds         r$   4apply_custom_provider_extra_headers_to_client_kwargsr  C  sh     6h@PRXYYM -##$566<"==F
MM-   '-M#$$$rZ   c                    | r|sdS |T	 t          |          }nC# t          $ r6 |Y dS |                    d          }t          |t                    r|ng }Y nw xY wt          |t                    sdS |pd                    d          }|sdS |D ]}t          |t                    s|                    d          pd                    d          }|r||k    rK|                    d          }t          |t                    sv|                    |           }	t          |	t                    s|	                    d          }
|
	 t          |
          }n# t          t          f$ r Y w xY w|dk    r|c S dS )	a  Look up a per-model ``context_length`` override from ``custom_providers``.

    Matches any entry whose ``base_url`` equals ``base_url`` (trailing-slash
    insensitive) and returns ``custom_providers[i].models.<model>.context_length``
    if present and valid.  Returns ``None`` when no override applies.

    This is the single source of truth for custom-provider context overrides,
    used by:
      * ``AIAgent.__init__`` (startup resolution)
      * ``AIAgent.switch_model`` (mid-session ``/model`` switch)
      * ``hermes_cli.model_switch.resolve_display_context_length`` (``/model`` confirmation display)
      * ``gateway.run._format_session_info`` (``/info`` display)
      * ``agent.model_metadata.get_model_context_length`` (when custom_providers is threaded through)

    Before this helper existed, the lookup was duplicated in ``run_agent.py``'s
    startup path only; every other path (notably ``/model`` switch) fell back
    to the 128K default.  See #15779.
    Nr  r   r  r(  rp  re  r   )
r  r   r   r0  r   r  r2  rU  r1  rX   )r  r(  r  r5  r   r  ra  r  rp  r?  raw_ctxctxs               r$   "get_custom_provider_context_lengthr  Z  s   0   t	D>vFF 	D 	D 	D~tt**/00C&0d&;&;Css		D
 &-- t.b((--J t!  %&& 	YYz**0b88==	 	I338$$&$'' 	JJu%%	)T** 	-- 011?	g,,CC:& 	 	 	H	77JJJ 4s&    A.AAEE/.E/c                     t          | t                    rdS 	 t          |           }n# t          t          f$ r Y dS w xY wt          |d          S )zHReturn a safe integer config version, treating invalid values as legacy.r   )r0  r  rU  r1  rX   max)rB  versions     r$   _coerce_config_versionr    s`    % qe**z"   qqw??s   ) >>c                     t          t                              dd                    pd} t                      }|                                s| | fS 	 t          |d          5 }t          |          pi }ddd           n# 1 swxY w Y   n+# t          $ r}t          ||           | | fcY d}~S d}~ww xY wt          |t                    si }t          |                    d                    }|| fS )a  
    Check the raw on-disk config schema version.

    ``load_config()`` deliberately starts from ``DEFAULT_CONFIG`` and deep-merges
    the user's file, which is correct for runtime reads but wrong for deciding
    whether the user's persisted schema has been migrated. A config file with no
    raw ``_config_version`` must remain visible as legacy instead of inheriting
    the latest default version in memory.

    Returns (current_version, latest_version).
    r  r  r   r   N)r  rK  r   rI  r   r9  rF  r   r6   r0  r2  )latestr   r?  r5  rT  r7  s         r$   check_config_versionr    sU    $N$6$67H!$L$LMMRQRF!##K v~+000 	-A#A&&,"F	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	- 	-    	#;222v~	 fd## $VZZ0A%B%BCCGF?sB   B #B5B BB B	B 
B5B0*B50B5>   r  r  r  r  r  r;  rE  r_  r|  r  r  r$  r  rR  r  r  mcp_serversfallback_modelr  r  r  r  >   r   r  rp  r)  rc  r  r(  r*  rq  rr  re  rf  >   r)  r  r(  rf  c                   2    e Zd ZU dZeed<   eed<   eed<   dS )ConfigIssuez$A detected config structure problem.severitymessagehintN)r4  
__module____qualname____doc__r+   __annotations__r   rZ   r$   r  r    s1         ..MMMLLL
IIIIIrZ   r  c                 d	   | 2	 t                      } n"# t          $ r t          ddd          gcY S w xY wg }|                     d          }|t	          |t
                    r|                    t          ddd                     t	          |t
                    r!t          |                                          nt                      }|t          z  }|r5|                    t          dd	t          |           d
d                     nt	          |t                    rt          |          D ]\  }}t	          |t
                    s>|                    t          dd| dt          |          j         dd                     X|                    d          s(|                    t          dd| dd                     |                    d          s(|                    t          dd| dd                     |                     d          }|t	          |t                    rt          |          D ]\  }}t	          |t
                    s=|                    t          dd| dt          |          j         d                     W|                    d          s(|                    t          dd| dd                     |                    d          s(|                    t          dd| dd                     nt	          |t
                    s:|                    t          dd t          |          j         d!                     nt|rr|                    d          s$|                    t          dd"d                     |                    d          s$|                    t          dd#d$                     t	          |t
                    r.d| vr*d|pi v r$|                    t          dd%d&                     |                     d          }|r&|s$|                    t          dd'd(                     | D ]V}	|	                    d)          r|	t           vr5|	t          v r,|                    t          dd*|	 d+d,|	 d-                     W|S ).a"  Validate config.yaml structure and return a list of detected issues.

    Catches common YAML formatting mistakes that produce confusing runtime
    errors (like "Unknown provider") instead of clear diagnostics.

    Can be called with a pre-loaded config dict, or will load from disk.
    NerrorzCould not load config.yamlz+Run 'hermes setup' to create a valid configr  uO   custom_providers is a dict — it must be a YAML list (items prefixed with '-')zeChange to:
  custom_providers:
    - name: my-provider
      base_url: https://...
      api_key: ...r/   zRoot-level keys z( look like custom_providers entry fieldszLThese should be indented under a '- name: ...' list entry, not at root levelzcustom_providers[z] is not a dict (got )z1Each entry should have at minimum: name, base_urlr   z] is missing 'name' fieldz#Add a name, e.g.: name: my-providerr(  z] is missing 'base_url' fieldzDAdd the API endpoint URL, e.g.: base_url: https://api.example.com/v1r  zfallback_model[z] should be a dict, got z!Each entry needs provider + modelr'  z] is missing 'provider' fieldz/Add: provider: openrouter (or another provider)r  z] is missing 'model' fieldzAdd: model: <model-name>zAfallback_model should be a dict with 'provider' and 'model', got zZChange to:
  fallback_model:
    provider: openrouter
    model: anthropic/claude-sonnet-4uH   fallback_model is missing 'provider' field — fallback will be disableduE   fallback_model is missing 'model' field — fallback will be disabledz8Add: model: anthropic/claude-sonnet-4 (or another model)zGfallback_model appears inside custom_providers instead of at root levelzDMove fallback_model to the top level of config.yaml (no indentation)u[   custom_providers defined but no 'model' section — Hermes won't know which provider to useziAdd a model section:
  model:
    provider: custom
    default: your-model-name
    base_url: https://...rA  zRoot-level key 'uW   ' looks misplaced — should it be under 'model:' or inside a 'custom_providers' entry?zMove 'z' under the appropriate section)rJ  r   r  r   r0  r2  r(  r  r  _CUSTOM_PROVIDER_LIKE_FIELDSr  r   	enumerater3  r4  r:  _KNOWN_ROOT_KEYS)
r5  issuescpcp_keys
suspiciousira  fbr?  r4   s
             r$   validate_config_structurer    s    ~	w ]]FF 	w 	w 	w)EGtuuvvvv	w !#F 
&	'	'B	~b$ (	MM+a%     )32t(<(<Gc"''))nnn#%%G #??J kcvj'9'9cccb    
 D!! 	%bMM  5!%.. MM+![A[[DKKDX[[[K# #   
 yy(( MM+!HAHHH=# #   
 yy,, MM+!LALLL^# #    
$	%	%B	~b$ +	%bMM  5!%.. MM+[![[T%[[EY[[;# #     !99Z00 k%NaNNNM' '   
 !99W-- k%KaKKK6' '   ( B%% 	MM+gTXY[T\T\Tegg7       	66*%% k^E    
 66'?? k[N     "d  0 > >CSXZX`^`CaCakUR
 
 	 	 	 

7##I	 	) 	ki(
 
 	 	 	   >># 	&&&32N+N+NMM+3====     Ms    22c                 T   	 t          |           }n# t          $ r Y dS w xY w|sdS dg}|D ]1}|j        dk    rdnd}|                    d| d|j                    2|                    d           t
          j                            d	                    |          d
z              dS )zPrint config structure warnings to stderr at startup.

    Called early in CLI and gateway init so users see problems before
    they hit cryptic "Unknown provider" errors.  Prints nothing if
    config is healthy.
    Nu3   [33m⚠ Config issues detected in config.yaml:[0mr  u   [31m✗[0mu   [33m⚠[0m  r   z2  [2mRun 'hermes doctor' for fix suggestions.[0mr*   

)	r  r   r  r(  r  r0   r1   r2   r  )r5  r  linescimarkers        r$   print_config_warningsr  z  s    *622    HIE 1 1)+)?)?%%EY/&//2://0000	LLKLLLJTYYu%%./////r  c                    t           j                            d          }t           j                            d          }| !	 t                      } n# t          $ r Y dS w xY w|                     di           }t          |t                    r|                    dd          nd}|dv}g }|r|                    d| d	           |r|s|                    d
| d	           |rt           j                            dd          }|                    dd           |                    d           |                    d| d           t          j
                            d                    |          dz              dS dS )u   Warn if MESSAGING_CWD or TERMINAL_CWD is set in .env instead of config.yaml.

    These env vars are deprecated — the canonical setting is terminal.cwd
    in config.yaml.  Prints a migration hint to stderr.
    MESSAGING_CWDTERMINAL_CWDNr  r  r)   >   r   r  r  r)   u     [33m⚠[0m MESSAGING_CWD=u&    found in .env — this is deprecated.u     [33m⚠[0m TERMINAL_CWD=rF   z	~/.hermesr   u/   [33m⚠ Deprecated .env settings detected:[0mzN  [2mMove to config.yaml instead:  terminal:\n    cwd: /your/project/path[0mz'  [2mThen remove the old entries from z	/.env[0mr*   r  )r   r8  r   rJ  r   r0  r2  r(  insertr0   r1   r2   r  )r5  messaging_cwdterminal_cwd_envterminal_cfg
config_cwdconfig_has_explicit_cwdr  	hint_paths           r$   warn_deprecated_cwd_env_varsr    s    JNN?33Mz~~n55~	 ]]FF 	 	 	FF	 ::j"--L1;L$1O1OX!!%---UXJ(0HHE 
#- # # #	
 	
 	
  
 7 
#1A # # #	
 	
 	
  
4JNN=+>>	QOPPP=	
 	
 	
 	PPPP	
 	
 	
 	
5))F233333
4 
4s   A 
AAc                 $    t          |            dS )ub  Persist a migrated config under the migration write invariant.

    THE INVARIANT (single source of truth for the whole migration pipeline):
    a migration may only persist values that DIFFER from the current schema
    default, plus explicit removals/renames of user data. Pure schema defaults
    are never materialised to disk — ``load_config()``'s deep-merge supplies
    them at read time, so writing them adds nothing and actively shadows future
    default changes (see ``save_config``'s docstring). Materialising defaults on
    every version bump is what rewrote hand-curated configs into full
    DEFAULT_CONFIG dumps (the "hermes update / hermes -p blows up my config"
    reports).

    Every migration step MUST route its write through this helper instead of
    calling ``save_config`` directly. It is a thin wrapper over
    ``save_config(config)`` (default-stripping ON); centralising the call makes
    the invariant impossible to regress one migration at a time. Correctness
    across seeds, non-default values, behaviour flips, and data transforms is
    verified by the migration parity tests.
    N)save_config)r5  s    r$   _persist_migrationr    s    ( rZ   interactivequietc           
      &;  o g g g d}	 t                      }|r|st          d| d           n# t          $ r Y nw xY wt                      \  }}|dk     rFt	                      }|                    di           }t          |t                    si }d|vrt          d          }t          d          }	|r7|	                                d	v r!d
|d<   |d         
                    d           n|	r_|		                                dv rI|		                                |d<   |d         
                    d|		                                 d           n d|d<   |d         
                    d           ||d<   t          |           |st          d|d                     |dk     rt	                      }d|vrt          j        dd          }
|
r]|
                                rI|
                                |d<   |d         
                    d|
                                 d           n d|d<   |d         
                    d           t          |           |s|d         pd}t          d|            |dk     rD	 t          d          }|r!t          dd           |st          d           n# t          $ r Y nw xY w|dk     rt	                      }|                    d           }t          |t                     r|r|                    d!i           }t          |t                    si }d"}|D ]}t          |t                    s|                    d#d          }|                    d$d          p-|                    d%d          p|                    d&d          pd}|sv|                                	                                                    d'd(                              d)d                              d*d          }d+|v r|                    d+d(          }d+|v |                    d(          }|sE	 d"d,lm}  ||          }|j        pd-                    d.d(          }n# t          $ r d/| }Y nw xY w|}|}||v r| d(| }|d0z  }||v t+          ||1          }||s|                    d#d2           |                    d3          d4v r|                    d3d2           |||<   |d0z  }|d"k    r||d!<   |                    d d2           t          |           |sqt          d5| d6           t!          |                                          | d2         D ]3}||         }t          d7| d8|                    d&d                      4|d9k     rLd:D ]I}	 t          |          }|r%t          |d           |st          d;| d<           :# t          $ r Y Fw xY w|d=k     r]t	                      }|                    d>i           }t          |t                    r#d?|v r|d?         }|                    d@dA          }t	                      }|                    d>i           } |                     d?d2           |dBv rSh dC}!||!v rJ|                    dAi           }"t          |"t                    rd?|"vr|                     dAi           }#||#d?<   nJ|                    |i           }$t          |$t                    rd?|$vr|                     |i           }%||%d?<   | |d><   t          |           |st          dD           |dEk     rt	                      }|                    di           }t          |t                    si }dF|vrEdG|dF<   ||d<   |d         
                    dH           t          |           |st          dI           |dJk     rEt	                      }|                    di           }t          |t                    si }|                    dK          }&t          |&t                    r|&r|                    dLi           }'t          |'t                    si }'|&                                D ]#\  }(})|(|'vri |'|(<   d|'|(         vr|)|'|(         d<   $|'|dL<   ||d<   t          |           |sCdM                    dN |&                                D                       }*t          dO|*            |d         
                    dP           |dQk     rt	                      }|                    dRi           }+t          |+t                    r|+                    dSd2          },|+                    dTd2          }-|+                    dUd2          }.g }/|,rt7          |,                                          r}|                    dVi           }0|0                    dRi           }1|1                    d?          s<t7          |,                                          |1d?<   |/
                    dW|,            |-rt7          |-                                          dXvr|                    dVi           }0|0                    dRi           }1|1                    d@          r|1                    d@          dYk    r<t7          |-                                          |1d@<   |/
                    dZ|-            |.rt7          |.                                          r}|                    dVi           }0|0                    dRi           }1|1                    d$          s<t7          |.                                          |1d$<   |/
                    d[|.            |/s|,|-|.M|+|dR<   t          |           |s7|/r&t          d\dM                    |/                      nt          d]           |d^k     r!t	                      }|                    d_          }2t          |2t                    si }2d`|2vr|2                    dag           pg }3t          |3t                     sg }3t9          |3          }4g }5	 t;                      d_z  }6|6                                rt?          |6                                           D ]}7|7                                s|7dbz  }8|8!                                s|7dcz  }8|8!                                sJ	 tE          |8dde          5 }9tG          |9          pi }:d2d2d2           n# 1 swxY w Y   n# t          $ r i }:Y nw xY w|:                    d#          p|7j$        };|;|4v r|5
                    |;           n# t          $ r g }5Y nw xY w|5|2d`<   |2|d_<   t          |           |d         
                    dftK          |5           dg           |s2|5r!t          dhtK          |5           di           nt          dj           |dkk     r	 t;                      dlz  dmz  }<|<&                    dGdGn           n8# t          $ r+}=|do         
                    dp|< d8|=            Y d2}=~=nd2}=~=ww xY wt	                      }dq}>tN                              dmi           }?|                    dm          }@t          |@t                    si }@g }A|?                                D ]5\  }B}C|B|@vr,tQ          j)        |C          |@|B<   |A
                    |B           6|Ar|@|dm<   dG}>tN                              dVi                               dmi           }D|                    dV          }Et          |Et                    si }E|E                    dm          }Ft          |Ft                    si }Fg }G|D                                D ]5\  }B}C|B|Fvr,tQ          j)        |C          |F|B<   |G
                    |B           6|Gr|F|Edm<   |E|dV<   dG}>|>rt          |           |ArT|d         
                    drtK          |A           ds           |s&t          dtdM                    |A           du           |GrT|d         
                    dvtK          |G           ds           |s&t          dwdM                    |G           du           |dxk     rt	                      }|                    dy          }Ht          |Ht                    r^|H                    dz          d{k    rEd0|Hdz<   |H|dy<   t          |           |d         
                    d|           |st          d}           |d~k     rt	                      }dq}>dD ]}I|                    |I          }Jt          |Jt                    rd|Jvr1|J                    d          }Kt          |Kt6                    r&|K                                	                                n|K}L|Ldk    |Jd<   |J||I<   dG}>|d         
                    |I d|Jd                     |>r t          |           |st          d           |dk     rt	                      }|                    d          }Mt          |Mt                    si }M|M                    d          }Nt          |Nt6                    o)|N                                	                                dYk    }O|N|OrEdq|Md<   |M|d<   t          |           |d         
                    d           |st          d           |dk     rt	                      }|                    d          }Mt          |Mt                    r\|M                    d          dGu rEdq|Md<   |M|d<   t          |           |d         
                    d           |st          d           |dk     rt	                      }|                    d          }Pt          |Pt                    rd|Pv r|P                    d          }Q	 tU          |Q          }Rn# tV          tX          f$ r d2}RY nw xY w|Rn|Rdk    rh	 tU          |P                    dd                    }Sn# tV          tX          f$ r d}SY nw xY w|R|Sk    r$|R|Pd<   |d         
                    d|R d           |P|d<   t          |           |st          d           t	                      }|                    d          }Tt          |Tt                    r	 d"dl-m.}U n# t          $ r d2}UY nw xY w|Urdq}V|T                                D ]|\  }W}t          |t                    s |U|W|          }X|Xs*dq|d`<   dG}V|do         
                    d|W d           |s*|XD ]}Yt          d|Y            t          d|W d           }|Vr|T|d<   t          |           	 d"dl/m0}Z d"dl1m2}[  |[t	                                          d          |Z          }\|\D ]1}]|do         
                    |]           |st          d|]            2n2# t          $ r%}^tf          4                    d|^           Y d2}^~^nd2}^~^ww xY w||k     r|st          d| d|            tk          dG          }_|_r7|s5t          d           |_D ]#}`t          d|`d#          d8|`d                     $| r%|_r"t          d           |_D ]}`|`                    d%          rt          d|`d%                     |`                    d          rtm          d|`d          d8          }an+to          d|`d          d8                                          }a|arPt          |`d#         |a           |d         
                    |`d#                    t          d|`d#                     n%|do         
                    d|`d#          d           t                       tk          dq          }b|_rd |_D             nt9                      oofd|bD             }bt9                      }ctq          |d0z   |d0z             D ]0}d|c9                    tt                              |dg                      1|cr9| r6|s3d t?          |c          D             }e|ert          dtK          |e           d           |eD ].\  };}ft          d|; d|f                    dd                      /t                       	 to          d                                          	                                }gn# tv          tx          f$ r d}gY nw xY w|gdv rVt                       |eD ]C\  };}f|f                    d%          r?t          d|f                    d|;                      t          d|fd%                     n&t          d|f                    d|;                      |f                    d          r(tm          d|f                    d|;           d          }an9to          d|f                    d|;           d                                          }a|ar=t          |;|a           |d         
                    |;           t          d|;            t                       Ent          d           t{                      }h|hr%|d         >                    d |hD                        ||k     r"t	                      }||d<   t          |           t                      }i|ir*| r'|s$t          dtK          |i           d           |iD ]=}`|`                    dd          }jt          d|`d          d|`d          d|j d*           >t                       	 to          d                                          	                                }gn# tv          tx          f$ r d}gY nw xY w|gdv rTt                       t	                      }	 d"dl@mA}k n# t          $ r d}kY nw xY w|iD ]
}`|`                    dd          }l|lrd|l d*nd}mto          d|`d          |m d8                                          }a|as|lrt7          |l          }a|ar[|k d.|`d          }nt          ||n|a           |d         
                    |`d                    t          d|`d          d|a            n<|do         
                    d|`d          d|`                    ddȦ           dɝ           t                       t          |           nt          d           |S )a<  
    Migrate config to latest version, prompting for new required fields.
    
    Args:
        interactive: If True, prompt user for missing values
        quiet: If True, suppress output
        
    Returns:
        Dict with migration results: {"env_added": [...], "config_added": [...], "warnings": [...]}
    )	env_addedconfig_addedwarningsu     ✓ Repaired .env file (z corrupted entries fixed)r  r;  tool_progressr   r   >   r  r  r  r  r  z;display.tool_progress=off (from HERMES_TOOL_PROGRESS=false)>   allnewverbosezdisplay.tool_progress=z! (from HERMES_TOOL_PROGRESS_MODE)r	  z#display.tool_progress=all (default)u-     ✓ Migrated tool progress to config.yaml: r  r  HERMES_TIMEZONEr   z	timezone=z (from HERMES_TIMEZONE)z$timezone= (empty, uses server-local)(server-local)u%     ✓ Added timezone to config.yaml: 	   rn   u8     ✓ Cleared ANTHROPIC_TOKEN from .env (no longer used)   r  r  r   r   r(  rA  rA  r   r   (r  z--ry  endpointr)   z	endpoint-r  r`  Nr)  >   no-keyno-key-requiredr   u     ✓ Migrated z) custom provider(s) to providers: sectionu       → r(      )	LLM_MODELOPENAI_MODELu     ✓ Cleared u>    from .env (no longer used — config.yaml is source of truth)   r  r  r'  r  >   r  local_command>   base.entiny.enlarge-v1large-v2large-v3small.en	medium.enlarge-v3-turbodistil-large-v2distil-large-v3distil-small.endistil-medium.endistil-large-v3.5r  tinylargesmallturbomediumu;     ✓ Migrated legacy stt.model to provider-specific config   rf  Tz1display.interim_assistant_messages=true (default)u3     ✓ Added display.interim_assistant_messages=truer  rr  ry  rx  c              3   *   K   | ]\  }}| d | V  dS )r1  Nr   )r~  pms      r$   	<genexpr>z!migrate_config.<locals>.<genexpr>  s0      $R$RDAqZZAZZ$R$R$R$R$R$RrZ   u>     ✓ Migrated tool_progress_overrides → display.platforms: z9display.platforms (migrated from tool_progress_overrides)   r  summary_modelsummary_providersummary_base_urlr$  model=>   r   r  r  	provider=z	base_url=u@     ✓ Migrated compression.summary_* → auxiliary.compression: u/     ✓ Removed unused compression.summary_* keys   pluginsr  r  plugin.yaml
plugin.ymlr   r   z$plugins.enabled (opt-in allow-list, z grandfathered)u(     ✓ Plugins now opt-in: grandfathered z( existing plugin(s) into plugins.enabledum     ✓ Plugins now opt-in: no existing plugins to grandfather. Use `hermes plugins enable <name>` to activate.   r}  r  r  r  zCould not create Fz	curator (z default key(s))u&     ✓ Curator settings now available (u"   ) — edit via `hermes config set`zauxiliary.curator (u0     ✓ auxiliary.curator settings now available (   r@  rB  r  u   model_catalog.ttl_hours 24→1uB     ✓ Lowered model_catalog.ttl_hours to 1 (hourly picker refresh)   )r  r  
write_modeapprover  u   .write_mode → write_approval=u:     ✓ Renamed write_mode → write_approval (boolean gate)   r  r  zagent.verify_on_stop=falseu     ✓ Turned off verify-on-stop (agent.verify_on_stop: false). Set it to true to re-enable, or "auto" for the legacy surface-aware behavior.    u     ✓ Turned off verify-on-stop (agent.verify_on_stop: false) — the old default was written into your config as a literal true. Set it to true again to re-enable, or "auto" for the legacy surface-aware behavior.rz  r  max_async_childrenr  r  z#delegation.max_concurrent_children=z, (folded from deprecated max_async_children)u     ✓ Removed deprecated delegation.max_async_children — delegation.max_concurrent_children now caps background delegations too.r  )validate_mcp_server_entryz Disabled suspicious MCP server ''u     ⚠ u     ⚠ Disabled MCP server 'z' pending review)validate_toolset)validate_platform_toolsetsplatform_toolsetsz(platform_toolsets validation skipped: %szConfig version:     → r"  u0   
⚠️  Missing required environment variables:u      • r  z
Let's configure them now:
z  Get your key at: r  r  r  r  u     ✓ Saved zSkipped z - some features may not workc                     h | ]
}|d          S )r   r   r~  r  s     r$   	<setcomp>z!migrate_config.<locals>.<setcomp>g  s    555Aai555rZ   c                 R    g | ]#}|d          v|                     d          !|$S )r   r  r   )r~  r  required_namess     r$   
<listcomp>z"migrate_config.<locals>.<listcomp>h  sC       V9N**1553D3D* 	
***rZ   c                 \    g | ])}t          |          s|t          v |t          |         f*S r   )r'  r)  )r~  r   s     r$   rO  z"migrate_config.<locals>.<listcomp>s  sK     
 
 
 &&
 ,03D+D+D $T*++D+D+DrZ   
  z$ new optional key(s) in this update:u       • u    — z  Configure new keys? [y/N]: n>   yr   z (Enter to skip): z1  Set later with: hermes config set <key> <value>c              3   &   K   | ]}|d          V  dS )r4   Nr   )r~  r  s     r$   r/  z!migrate_config.<locals>.<genexpr>  s&      &P&PuU|&P&P&P&P&P&PrZ   r  z! skill setting(s) not configured:r  r  r4   z (from skill: z#  Configure skill settings? [y/N]: )rO  zskills.configr^  z (default:  = u    — skill 'r]  z' may ask for it later)Csanitize_env_filer,  r   r  read_raw_configr   r0  r2  r'  r   r(  r  r   r   r   save_env_valuer   r   r  rz  hostnamer  rB  r  
setdefaultr&  r  r+   r  r   r   r  iterdirr   r9  rF  r   lenr
  rK  copydeepcopyrU  r1  rX   hermes_cli.mcp_securityrB  r  rD  hermes_cli.toolset_validationrE  r.   rR  r,  r   inputranger  r  EOFErrorKeyboardInterruptrL  extendrW  rP  rO  r;  )pr  r  resultsfixescurrent_ver
latest_verr5  r;  old_enabledold_modeold_tz
tz_display	old_tokencustom_listr  migrated_countra  old_nameold_urlr4   rz  r  base_keysuffix	new_entryepdead_varold_valr   raw_sttlegacy_modelr'  r  _local_models	raw_local	local_cfgraw_providerprovider_cfgold_overridesry  platrf  migratedcomps_model
s_provider
s_base_urlmigrated_keysauxaux_compplugins_cfgr  disabled_setgrandfathereduser_plugins_dirchildmanifest_file_mfmanifestr   curator_dirrT  touched_curator_defaultsraw_curatoradded_curatorr  r  _aux_curator_defaultsraw_auxraw_aux_curator	added_auxraw_mc	subsystemsuboldold_norm	raw_agentcuris_auto_sentinel	raw_deleg	old_asyncold_async_icur_childrenraw_mcp_servers_validate_mcp_server_entrymcp_touchedserver_namer  issuerD  rE  ts_warningsw_ts_val_errmissing_envrU  rB  missing_optionalnew_var_namesvernew_and_unsetr>  answermissing_configmissing_skill_config
skill_namerO  r^  default_hintrV  rN  sp                                                                                                                  @r$   migrate_configr    s#    CCG!## 	Q 	QOuOOOPPP    344K Q ""**Y++'4(( 	G'))'(>??K$%@AAH V{00226JJJ+0('../lmmmm Vhnn..2KKK+3>>+;+;('../{HXHX/{/{/{||||+0('../TUUU 'F9v&&& b`goF^``aaa Q ""V##Y0"55F W&,,.. W%+\\^^z"'../b6<<>>/b/b/bcccc%'z"'../UVVVv&&& L#J/C3C
JjJJKKK Q	%&788I V0"555 VTUUU 	 	 	D	 R ""jj!344k4(( :	E[ :	E#ZZR88Nnd33 $!#N$ *$ *$!%..  99VR00))J33iuyy7K7KiuyyY^`bOcOcigi  nn&&,,..66sC@@HHbQQYYZ]_abbckk++dC00C ckkiinn ;;999999!)'!2!2%<*EEc3OO$ ; ; ;:.::; '^++%0000CaKF ^++ F!$  	 $ 0MM&$///==++/PPPMM)T222&/s#!#!!&4{#

-t444"6*** EeNeeefff#N$7$7$9$9::N?;K;KL E E+C0CCCub0A0ACCDDDD R5 	 	H'11 y"8R000  ywxwwwxxx    R ''%$$gt$$ %	UG););"7+L{{:w77H$&&F**UB''CGGGT""" 555! ! !  =00 !(GR 8 8I%i66 :':R:R$'NN7B$?$?	-9	'*  '{{8R88!,55 99T9T#&>>(B#?#?L,8L)F5Mv&&& USTTT R ""**Y++'4(( 	G'w6648G01 'F9N#**+^___v&&& MKLLL R ""**Y++'4(( 	G$=>>mT** 	h} 	hK44Ii.. 	+1133 < <
dy((&(IdO")D/997;IdOO4#,GK  'F9v&&& c99$R$RM<O<O<Q<Q$R$R$RRRaW_aabbbN#**+fggg R ""zz-,,dD!! 	Qhh55G"4d;;J"4d;;JM =3w<<--// =''R88>>-<<||G,, =(+G(:(:(<(<HW%!(();'););<<< Cc*oo3355\II''R88>>-<<||J// C8<<
3K3Kv3U3U+.z??+@+@+B+BHZ(!(()AZ)A)ABBB Cc*oo3355 C''R88>>-<<||J// C+.z??+@+@+B+BHZ(!(()AZ)A)ABBB Q 3z7MQ[Qg(,}%"6*** Q$ Q{aeajajkxayay{{||||OPPP R ""jj+++t,, 	KK''"z266<"Hh-- x==L (*M##2#4#4y#@ #**,, 3!'(8(@(@(B(B!C!C 3 3$||~~ %$(-(=,3355 A,1L,@M,3355 %$*!%mg!F!F!F E#+9#+>+>+D"E E E E E E E E E E E E E E E( * * *')HHH*'||F33Auz<//$%,,T2222 # # # "# &3K	" +F9v&&&N#**Zs=7I7IZZZ    
  	X}--X X X   
 J  0 R	O)++f4y@KdT:::: 	O 	O 	OJ&&'M;'M'M!'M'MNNNNNNNN	O !"" +..y"==jj+++t,, 	K#%%++-- 	( 	(DAq##!%q!1!1A$$Q''' 	 +F9G {B//33IrBB 	 **[))'4(( 	G!++i00/400 	! O!	)//11 	$ 	$DAq''%)]1%5%5"  ### 	!0GI")F;G 	v&&& '..DM 2 2DDD    Y IIm44Y Y Y    '..J#i..JJJ    U IIi00U U U   R ""O,,fd## 	\

;(?(?2(E(E"#F;&,F?#v&&&N#**+KLLL \Z[[[ R ""- 	 	I**Y''Cc4(( L,C,C'',''C.8c.B.BKsyy{{((***H%-%:C ! #F9GN#**TTSAQ=RTT     	Tv&&& TRSSS& R ""JJw''	)T** 	Imm,--sC  BSYY[[%6%6%8%8F%B 	 ;*;*/I&''F7Ov&&&N#**+GHHH .  $ R ""JJw''	i&& 	9==9I+J+Jd+R+R*/I&''F7Ov&&&N#**+GHHH 5   R ""JJ|,,	i&& 	+?9+L+L!&:;;I#!)nnz* # # #"#&;??%#&y}}5NPQ'R'R#S#SLL!:. % % %#$LLL%--;FI78N+22Gk G G G   $-F< v&&& '   Fjj//O/4(( +	.ggggggg 	. 	. 	.)-&&&	.% 	+K&5&;&;&=&= W W"U!%.. 33KGG #(i "
#**E{EEE    W!' 0 0.u..////UUUUVVV +(7}%"6***N------LLLLLL00!!"5668H
 
  	$ 	$AJ&&q))) $lqll###	$  N N N?MMMMMMMMN Z???:??@@@ 'T:::K A5 AABBB 	A 	AC?CK??3}+=??@@@@ { -... 	 	Cwwu~~ :8CJ88999wwz"" >,-C#h--C-C-CDD43x=44455;;== bs6{E222$++CK8882S[223333
#**+`c&k+`+`+`aaaGGGG ,%@@@9DO555555#%%N   #   EEM[1_j1n55 ? ?044S"==>>>> $K $KU $K
 
}--
 
 

  	KQ]++QQQRRR+ K K
dIIIDHH]B,G,GIIJJJJGGG>??EEGGMMOO/0    %%"/  JD$xx DB488M4#@#@BBCCCADKAABBBBB488M4#@#@BBCCCxx
++ a 4M(D!9!9MMM! ! !&&W488Hd+C+C&W&W&W X X ^ ^ ` ` 5&tU333,33D9993T33444GGGG!$ IJJJ /00N Q&&&P&P&P&P&PPPPZ ""$. !6""" 9:: $G $GE $GQS-..QQQRRR' 	_ 	_C)44J]SZ]]c-.@]]PZ]]]^^^^	@AAGGIIOOQQFF+, 	 	 	FFF	 \!!GGG$&&F6AAAAAAA 6 6 6&5###6+  '')R00;BJ7W7777B3x=B,BBBCCIIKK ) )LLE %8"G"G3u:"G"GKU;;;N+223u:>>>?U????@@@@J'..h3u:hh3777C;P;Phhh   v&&&&EFFFNsf  %/ 
<<=2J0 0
J=<J=.Q11RR26W))
W65W6Bv%  u1uuu	uu	uv% u*'v% )u**:v% %v43v48+y$ $
z.!zzQAQ QAQ,Q+AQ,Q8#AR RAR2R1AR2T=AU UAUUAUXA+AY. Y.
AZY8AZZAZe3Ae6 e6AffAfo3Ap pAp&p%Ap&qAq qAq!q Aq!overridec                    |                                  }|                                D ]X\  }}||v rJt          ||         t                    r/t          |t                    rt	          ||         |          ||<   S|||<   Y|S )a  Recursively merge *override* into *base*, preserving nested defaults.

    Keys in *override* take precedence. If both values are dicts the merge
    recurses, so a user who overrides only ``tts.elevenlabs.voice_id`` will
    keep the default ``tts.elevenlabs.model_id`` intact.
    )r]  r&  r0  r2  _deep_merge)r  r  resultr4   rB  s        r$   r  r    s     YY[[Fnn&&    
U6MM6#;-- 5$''  &fSk599F3KKF3KKMrZ   cfgdotted_keysc                 <   t                      }|D ]}|                    d          }| }|dd         D ]'}t          |t                    r||vrd} n	||         }(t          |t                    r(|d         |v r||d         = |                    |           | |fS )a}  Remove the given dotted leaf keys from a nested config dict.

    Returns ``(pruned_cfg, set_of_stripped_keys_that_were_present)``. Used by
    ``save_config`` to drop managed-scope leaves before persisting, so a bulk
    write never writes a user value that would lose to the managed layer on the
    next load. Only keys actually present in ``cfg`` are reported as stripped.
    r)   Nr\  )r  r/  r0  r2  r-   )r  r  strippeddottedr6  noder-  s          r$   _strip_dotted_keysr    s     EEH 
! 
!S!!ss 	 	AdD)) Qd]]7DDdD!! 	!eBi4&7&7U2YLL   =rZ   c                    t          | t                    rt          j        dd |           S t          | t                    rd |                                 D             S t          | t                    rd | D             S | S )a  Recursively expand ``${VAR}`` references in config values.

    Only string values are processed; dict keys, numbers, booleans, and
    None are left untouched.  Unresolved references (variable not in
    ``os.environ``) are kept verbatim so callers can detect them.
    \${([^}]+)}c                     t           j                            |                     d          |                     d                    S )Nr  r   )r   r8  r   group)r.  s    r$   <lambda>z"_expand_env_vars.<locals>.<lambda>  s*    bjnnQWWQZZ<< rZ   c                 4    i | ]\  }}|t          |          S r   _expand_env_varsr}  s      r$   r  z$_expand_env_vars.<locals>.<dictcomp>  s'    ???41a#A&&???rZ   c                 ,    g | ]}t          |          S r   r  )r~  r  s     r$   rO  z$_expand_env_vars.<locals>.<listcomp>  s!    7774 &&777rZ   )r0  r+   r  r  r2  r&  r   )objs    r$   r  r    s     #s 
v<<
 
 	

 #t @??399;;????#t 87737777JrZ   c                 |   |i }t          | t                    r;t          j        d|           D ]$}t          j                            |          ||<   %ngt          | t                    r(|                                 D ]}t          ||           n*t          | t                    r| D ]}t          ||           |S )u  Map every ``${VAR}`` name referenced in config values to its current
    ``os.environ`` value (``None`` when unset).

    Stored alongside cached ``load_config()`` results so a cache hit can
    detect that the cached expansion was made against a *different*
    environment — e.g. a ``load_config()`` that ran before
    ``load_hermes_dotenv()`` populated the process env, or an env var
    rotated in-process after the first load. File mtime/size alone cannot
    see either case (#58514).
    Nr  )r0  r+   r  findallr   r8  r   r2  values_env_ref_snapshotr   )r  snapshotr   rB  r  s        r$   r  r    s     #s .J~s33 	2 	2DZ^^D11HTNN	2	C		 .ZZ\\ 	/ 	/EeX....	/	C		 . 	. 	.DdH----OrZ   c                     t          | t                    sdS i }| D ]V}t          |t                    r(t          |                    d          t                    s dS |d         }||v r dS |||<   W|S )zHReturn a name-indexed dict only when all items have unique string names.Nr   )r0  r   r2  r   r+   )r&  indexedr  r   s       r$   _items_by_unique_namer  .  s    eT"" tG  $%% 	Z8H8H#-N-N 	44F|7??44NrZ   c                    t          | t                    rft          t                    rQt          j        d          r<| k    rS t          t                    r| k    rS t	                    | k    rS | S t          | t
                    r6t          t
                    r!fd|                                 D             S t          | t                    rqt          t                    r\t          |           }t                    t                    |fd| D             S fdt          |           D             S | S )a  Restore raw ``${VAR}`` templates when a value is otherwise unchanged.

    ``load_config()`` expands env refs for runtime use. When a caller later
    persists that config after modifying some unrelated setting, keep the
    original on-disk template instead of writing the expanded plaintext
    secret back to ``config.yaml``.

    Prefer preserving the raw template when ``current`` still matches either
    the value previously returned by ``load_config()`` for this config path or
    the current environment expansion of ``raw``. This handles env-var
    rotation between load and save while still treating mixed literal/template
    string edits as caller-owned once their rendered value diverges.
    z	\${[^}]+}c                     i | ]T\  }}|t          |                    |          t          t                    r                    |          nd           US r  )_preserve_env_ref_templatesr   r0  r2  )r~  r4   rB  loaded_expandedr   s      r$   r  z/_preserve_env_ref_templates.<locals>.<dictcomp>U  sr     
 
 
 U ,,6,M,MW##C(((SW 
 
 
rZ   Nc                     g | ]c}t          |                    |                    d                     (                    |                    d                     nd          dS )r   N)r  r   )r~  r  loaded_by_nameraw_by_names     r$   rO  z/_preserve_env_ref_templates.<locals>.<listcomp>g  s{         ,OODHHV$4$455<J<VN&&txx'7'7888\`   rZ   c           
          g | ]a\  }}t          ||t                    k     r|         nd t          t                    r|t                    k     r|         nd           bS r  )r  r\  r0  r   )r~  indexr  r  r   s      r$   rO  z/_preserve_env_ref_templates.<locals>.<listcomp>o  s     	
 	
 	
 t (#c#hh..E

Dot449>_AUAU9U9U  && 	
 	
 	
rZ   )
r0  r+   r  r  r  r2  r&  r   r  r  )r7  r   r  current_by_namer  r  s    `` @@r$   r  r  =  s    '3 JsC$8$8 RY|UX=Y=Y c>>Jos++ 	?0J0JJC  G++J'4   
ZT%:%: 

 
 
 
 
 &mmoo
 
 
 	
 '4   
ZT%:%: 

 088+C00.??&;+B     $   	
 	
 	
 	
 	
  )11	
 	
 	
 		
 NrZ   .c                     t                      dt          dt          t          df         ddffd | d           S )a  Return leaf paths explicitly present in a raw config dict.

    Computed on the **raw** (un-normalized, un-expanded) config so that
    values injected by normalisation (e.g. ``agent.max_turns`` from
    ``DEFAULT_CONFIG``) are not mistakenly treated as user-set.

    Used by ``save_config`` to build the *preserve* set passed to
    ``_strip_default_values`` so only user-authored keys survive the
    defaults-strip pass.
    rB  r  .r   Nc                     t          | t                    r,|                                 D ]\  }} |||fz              d S |r                    |           d S d S r  )r0  r2  r&  r-   )rB  r  r4   r  _walkpathss       r$   r  z%_explicit_config_paths.<locals>._walk  su    eT"" 	#kkmm , ,
UeTSF]++++F 	IIdOOOOO	 	rZ   r   )r  r   r	   r+   )r5  r  r  s    @@r$   _explicit_config_pathsr  }  sh     #&%%ES c3h D        
E&"LrZ   rE  preserve_keysc                    dht          pd          z  dt          dt          dt          t          df         dt          ffdi }|                                 D ]-\  }} ||                    |          |f          }||||<   .|S )	a  Return *config* without keys whose values match *defaults*.

    Keys in *preserve_keys* (explicitly present in the user's raw config,
    before any normalisation) are always kept even when they equal the
    default, so user-set values such as ``memory.user_char_limit: 2200``
    survive a ``save_config`` round-trip.

    Nested dicts whose every child is stripped are removed entirely so
    default-only subtrees (e.g. ``gateway``) never bloat ``config.yaml``
    when the user has nothing to say about them.
    r  r   rB  r^  r  .r   c                 j   |
v rt          j        |           S t          | t                    rj| rht          |t                    r|ni }i }|                                 D ]2\  }}|                    |          } 	||||fz             }||||<   3|r|S d S | |k    rd S t          j        |           S r  )r]  r^  r0  r2  r&  r   )rB  r^  r  default_dictr  r4   r  child_defaultstripped_child_stripr  s            r$   r  z%_strip_default_values.<locals>._strip  s    =  ='''eT"" 	u 	&0$&?&?G77RL')H#kkmm 3 3
U , 0 0 5 5!'}dcVm!L!L!-$2HSM  4G4}U###rZ   )r  r   r	   r+   r&  r   )r5  rE  r  r  r4   rB  r  r  s     `    @r$   _strip_default_valuesr    s      **S1D"-E-EEM$c $C $uS#X $3 $ $ $ $ $ $ $,  Fllnn # #
U6%c!2!2SF;;"F3KMrZ   c                                           d          }t          |t                    o|                     d          }t          |t                    o)|                     d          p|                     d          }t           fddD                       }|s|s|s S t                                            d          }t          |t                    s	|rd|ini }nt          |          }| d<   dD ]I}                      |          }|r|                     |          s|||<                        |d           J                      d          |                     d          fD ]}|r|                     d	          s||d	<                        dd           |                    dd           |                     d          s2	 |                     d          p|                     d          }	|	r|	|d<   |                     d          r,|                    dd           |                    dd            S )
u  Move stale root-level provider/base_url/context_length into model section.

    Some users (or older code) placed ``provider:``, ``base_url:``, or
    ``context_length:`` at the config root instead of inside ``model:``.
    These root-level keys are only used as a fallback when the corresponding
    ``model.*`` key is empty — they never override an existing value.
    After migration the root-level keys are removed so they can't cause
    confusion on subsequent loads.

    Also aliases ``api_base`` → ``base_url`` (issue #8919). ``api_base`` is the
    intuitive name OpenAI-SDK / LiteLLM users reach for, and ``hermes config set``
    blindly accepts any dotted key — so ``model.api_base`` got written, confirmed,
    and then silently ignored by the runtime resolver (which reads only
    ``model.base_url``), causing requests to fall back to OpenRouter. We migrate
    the alias to the canonical key (fallback-only — never override an explicit
    ``base_url``) and drop the alias so it can't confuse later loads.

    Finally, canonicalizes the model-id key to ``model.default`` (issue #34500).
    The runtime resolver and ~14 other readers select the chat model via
    ``model.default``; ``model.model`` was already aliased inline at some sites
    but ``model.name`` was not, so a custom-provider config like
    ``model: {name: <id>, provider: <custom>}`` resolved to an empty model and
    the API request went out with ``model=`` (HTTP 400 from OpenAI-compatible
    backends) — while display paths (``hermes status``/``dump``) read ``name``
    and *showed* the model, making the failure silent. Normalizing here (the
    single load/save chokepoint) means every reader, present and future, sees a
    populated ``default`` and the stale alias is migrated out of config.yaml on
    the next save. Precedence: ``default`` > ``model`` > ``name`` (never
    overrides an explicit ``default``, so existing configs are unaffected).
    r  api_baser   c              3   B   K   | ]}                     |          V  d S r  rM  )r~  r  r5  s     r$   r/  z-_normalize_root_model_keys.<locals>.<genexpr>  s>        

1     rZ   )r'  r(  re  r  r^  )r'  r(  re  Nr(  )r   r0  r2  anyrB  )
r5  model_inmodel_has_aliasmodel_needs_canonhas_rootr  r4   root_val	alias_valaliass
   `         r$   _normalize_root_model_keysr    s   B zz'""H 400MX\\*5M5MO
 #8T22 W5f!5!5      U    H  O 4E &\\FJJwEeT"" &+3E""UF7O9  ::c?? 	"EIIcNN 	"!E#J

3 jj,,eii
.C.CD * *	 	*UYYz22 	* )E*
JJz4   	IIj$
 IIi   % %		'""7eii&7&7 	%$E)yy  		'4   		&$MrZ   c                    t          |           } t          |                     d          pi           }d| v }d|v }|r|s| d         |d<   |s|snd|vrt          d         d         |d<   || d<   |                     dd           | S )u  Normalize legacy root-level max_turns into agent.max_turns.

    Only injects the schema default when the user actually set max_turns
    somewhere (root level or under ``agent``).  A bare ``load_config()``
    call that passes the result straight to ``save_config()`` should not
    materialise ``agent.max_turns`` in config.yaml when the user never set
    it — that makes the default sticky and blocks future schema changes.
    r  r  N)r2  r   rK  rB  )r5  agent_confighad_root	had_agents       r$   _normalize_max_turns_configr    s     &\\F

7++1r22Lf$H|+I 8	 8$*;$7[!
  II I	L	(	($27$;K$H[!"F7O
JJ{D!!!MrZ   )r^  r  c                    t          | t                    s|S | }|D ]+}t          |t                    s|c S ||vr|c S ||         },|S )u%  Traverse nested dict keys safely, returning ``default`` on any miss.

    Canonical helper for the ``cfg.get("X", {}).get("Y", default)`` pattern
    that appears 50+ times across the codebase. Handles three common gotchas
    in one place:

      1. Missing intermediate keys (returns ``default``, no KeyError).
      2. An intermediate value that's not a dict (e.g. a user wrote a string
         where a section was expected). Returns ``default`` instead of
         AttributeError on ``.get()``.
      3. ``cfg is None`` (callers sometimes pass ``load_config() or None``).

    Named ``cfg_get`` rather than ``cfg_path`` to avoid shadowing the
    ubiquitous ``cfg_path = _hermes_home / "config.yaml"`` local variable
    that appears in gateway/run.py, cron/scheduler.py, main.py, etc.

    Explicit ``None`` values are returned as-is (matches ``dict.get(key,
    default)`` semantics — ``default`` is only returned when the key is
    *absent*, not when it's present but set to ``None``).

    Examples:
        >>> cfg_get({"agent": {"reasoning_effort": "high"}}, "agent", "reasoning_effort")
        'high'
        >>> cfg_get({}, "agent", "reasoning_effort", default="medium")
        'medium'
        >>> cfg_get({"agent": "oops_a_string"}, "agent", "reasoning_effort", default="low")
        'low'
        >>> cfg_get(None, "anything", default=42)
        42
        >>> cfg_get({"a": {"b": None}}, "a", "b", default="def")  # explicit None preserved
        >>> cfg_get({"a": {"b": False}}, "a", "b", default=True)  # falsy values preserved
        False
    )r0  r2  )r  r^  r  r  r4   s        r$   cfg_getr  9  sm    D c4   D  $%% 	NNNd??NNNCyKrZ   c                     t           5  	 t                      } |                                 }|j        |j        f}n%# t
          t          f$ r i cY cddd           S w xY wt          |           }t          	                    |          }|4|dd         |k    r&t          j        |d                   cddd           S 	 t          | d          5 }t          |          pi }ddd           n# 1 swxY w Y   n5# t          $ r(}t          | |           i cY d}~cddd           S d}~ww xY wt!          |t"                    si }|d         |d         t          j        |          ft          |<   |cddd           S # 1 swxY w Y   dS )u  Read ~/.hermes/config.yaml as-is, without merging defaults or migrating.

    Returns the raw YAML dict, or ``{}`` if the file doesn't exist or can't
    be parsed.  Use this for lightweight config reads where you just need a
    single value and don't want the overhead of ``load_config()``'s deep-merge
    + migration pipeline.

    Cached on the config file's (mtime_ns, size) — same strategy as
    ``load_config()``. Returns a deepcopy on every call since some callers
    mutate the result before passing to ``save_config()``.
    Nr  r   r   r   r  )_CONFIG_LOCKrI  r   r,   r   r<  r   r+   r]   r   r]  r^  r9  rF  r   r6   r0  r2  )r   r   	cache_keypath_keycachedr?  datarT  s           r$   rW  rW  h  s    
  	)++K!!##B4II!7+ 	 	 	II       
	 {##"&&x00&!*	"9"9=++       	kG444 /%a((.B/ / / / / / / / / / / / / / / 	 	 	&{A666IIIII%        	 $%% 	D'0|Yq\4=QUCVCV&W(#/                 s   F 0;F AF AAF  C;C/#C;/C3	3C;6C3	7C;:F ;
D-D(D-F (D--AF  FFc                    | t                      } 	 |                                  n5# t          $ r Y dS t          $ r}t	          d|  d| d          |d}~ww xY w	 t          | d          5 }|                    d           ddd           dS # 1 swxY w Y   dS # t          $ r}t	          d|  d| d          |d}~ww xY w)z>Refuse to replace an existing config.yaml that cannot be read.NzRefusing to overwrite z+: existing config.yaml cannot be accessed (z3). Fix the file permissions or move it aside first.rbr  z': existing config.yaml cannot be read ()rI  r   r<  r   r  r9  rm  )r   r&   r?  s      r$   $require_readable_config_before_writer
    s   %''      I[ I II I I
 
 	+t$$ 	FF1III	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	   I[ I II I I
 
 	sU   ' 
A	AAAB -BB BB BB 
C'B>>Cr  kwargsc                 D    ddl m} t          |             || |fi | dS )a*  Fail-closed atomic write for ``config.yaml``.

    The single chokepoint every config-update path should use instead of
    calling :func:`utils.atomic_yaml_write` directly. It runs
    :func:`require_readable_config_before_write` first, so a full-file
    replacement can never silently clobber an existing ``config.yaml`` that
    degraded to an empty dict on read (permission error, broken mount,
    transient I/O). New-file creation still works when the path is absent.

    Root cause this guards: ``read_raw_config()`` returns ``{}`` for BOTH an
    absent file and an unreadable-but-present file. Callers that read then
    overwrite can't tell the two apart, so an unreadable config would be
    replaced with only defaults or the single edited section. Routing every
    write through this helper enforces the invariant in one place rather than
    relying on each of ~15 independent write sites to remember the guard.

    ``kwargs`` are forwarded verbatim to ``atomic_yaml_write``
    (``sort_keys``, ``default_flow_style``, ``extra_content``, ...).
    r   atomic_yaml_writeN)utilsr  r
  )r   r  r  r  s       r$   atomic_config_writer    sE    ( ('''''(555k422622222rZ   c                  "    t          d          S )u  Load configuration from ~/.hermes/config.yaml.

    Cached on the config file's (mtime_ns, size). Returns a deepcopy of
    the cached value when unchanged, since most call sites mutate the
    result (e.g. ``cfg["model"]["default"] = ...`` before ``save_config``).
    The cache is keyed on ``str(config_path)`` so profile switches
    (which change ``HERMES_HOME`` and therefore ``get_config_path()``)
    don't collide.

    Read-only callers should use ``load_config_readonly()`` to skip the
    defensive deepcopy — that path matters in agent-loop hot spots like
    ``get_provider_request_timeout`` which is called once per API turn.
    Twant_deepcopy_load_config_implr   rZ   r$   rJ  rJ    s     40000rZ   c                  "    t          d          S )u  Fast-path variant of ``load_config()`` for callers that ONLY READ.

    Returns the cached config dict directly without the defensive deepcopy
    that ``load_config()`` applies. **Mutating the returned dict (or any
    nested structure) corrupts the in-process cache for every subsequent
    caller** — only use this when you are absolutely sure your code path
    will not write to the result. If you need to mutate or pass to
    ``save_config``, call ``load_config()`` instead.

    Why this exists: ``load_config()`` cache-hit cost is ~265us per call,
    half of which (~135us) is the defensive deepcopy. The agent loop calls
    into config reads (timeouts, thresholds, feature flags) ~20-50x per
    conversation; skipping deepcopy here removes a measurable allocation
    source and the GC pressure that comes with it.

    Note: this returns a plain ``dict`` (not ``MappingProxyType``) so
    existing ``isinstance(x, dict)`` guards downstream keep working. The
    safety guarantee is purely documented, not enforced — be careful.
    Fr  r  r   rZ   r$   load_config_readonlyr    s    ( 51111rZ   )r   platform_key	field_keyr   c                2   |rt                      nt                      }|                    di           }t          |t                    si }||d<   |                    | i           }t          |t                    si }||| <   |||<   t          |           dS )a)  Persist one scalar field under ``platforms.<platform_key>``.

    ``raw=True`` preserves CLI setup flows that intentionally edit only the
    user's raw config file. Dashboard routes use the default loaded-config path
    so they retain their existing profile-scoped ``load_config`` behavior.
    ry  N)rW  rJ  rZ  r0  r2  r   )r  r  rB  r   r5  ry  platform_configs          r$   write_platform_config_fieldr    s     #&8_;==F!!+r22Ii&& (	'{**<<<Oot,, 2"1	,!&OIrZ   re   r  TERMINAL_TIMEOUTlifetime_secondsTERMINAL_LIFETIME_SECONDSTERMINAL_DOCKER_IMAGETERMINAL_DOCKER_FORWARD_ENVTERMINAL_SINGULARITY_IMAGETERMINAL_MODAL_IMAGETERMINAL_DAYTONA_IMAGEssh_hostTERMINAL_SSH_HOSTssh_userTERMINAL_SSH_USERssh_portr   ssh_keyrz   TERMINAL_CONTAINER_CPUTERMINAL_CONTAINER_MEMORYr  TERMINAL_CONTAINER_DISKTERMINAL_CONTAINER_PERSISTENTTERMINAL_DOCKER_VOLUMESTERMINAL_DOCKER_ENV&TERMINAL_DOCKER_MOUNT_CWD_TO_WORKSPACETERMINAL_DOCKER_NETWORKTERMINAL_DOCKER_EXTRA_ARGS TERMINAL_DOCKER_RUN_AS_HOST_USER(TERMINAL_DOCKER_PERSIST_ACROSS_PROCESSESTERMINAL_DOCKER_ORPHAN_REAPERTERMINAL_SANDBOX_DIRTERMINAL_PERSISTENT_SHELL)r  r  r  r  r  r  r  docker_persist_across_processesdocker_orphan_reapersandbox_dirr  c                     t          | t          t          f          rt          j        |           S t          |           S r  )r0  r   r2  jsondumpsr+   rB  s    r$   _terminal_env_valuer@  $  s4    %$&& !z%   u::rZ   c                     d}|                      |          sdS t                              | t          |          d                   S )z;Return the env var mirrored by a ``terminal.*`` config key.z	terminal.N)r:  TERMINAL_CONFIG_ENV_MAPr   r\  )r4   r  s     r$   terminal_config_env_var_for_keyrC  *  sC    F>>&!! t"&&s3v;;<<'8999rZ   )envr5  r  rD  c                    | t           j        n| }t                      }t          |                    d          t
                    }||n|}||nt                      }t          |t
                    r|                    di           ni }t          |t
                    s|S t                                          D ]\  }	}
|	|vr
||	         }|	dk    r\t          |pd          
                                }|dv r@t          |t                    rt           j                            |          }|s|
|vrt          |          ||
<   |S )a`  Bridge ``terminal.*`` config into the env vars terminal tools read.

    ``tools.terminal_tool`` is intentionally environment-driven because it also
    runs in child processes (TUI, dashboard PTY, gateway workers).  This helper
    gives those child-process launch paths the same config bridge as classic
    CLI without importing ``cli.py`` and paying for its startup side effects.

    When the user config contains a ``terminal`` section, config.yaml is
    authoritative and overrides existing env values.  Otherwise defaults only
    backfill missing env vars so exported/.env values keep working.
    Nr  r  r   >   r  r  r)   )r   r8  rW  r0  r   r2  r  rB  r&  r+   r   r  
expanduserr@  )rD  r5  r  target
raw_configfile_has_terminal_configshould_overrider  r  cfg_keyenv_varrB  raw_cwds                r$   apply_terminal_config_to_envrN  2  s\   " ;RZZCF ""J)*..*D*DdKK2:2B..O&&&,@,B,BC.8d.C.CK377:r***LlD)) 399;; 9 9,&&W%e%+2&&,,..G...%%% 2**511 	9gV331%88F7OMrZ   r  c                 N   t           5  t                       t                      }t          |          }	 |                                }|j        |j        f}n# t          $ r d }Y nw xY wddlm	} |
                                }|r|dz  nd }	 |r|                                nd }|r|j        |j        fnd}	n# t          $ r d}	Y nw xY w||d         |d         |	d         |	d         f}
n|	dk    rdd|	d         |	d         f}
nd }
t                              |          }||
|d d         |
k    rxt          |          dk    r|d         ni }t          d |                                D                       r0| rt#          j        |d                   n|d         cd d d            S t#          j        t&                    }|	 t)          |d	
          5 }t+          |          pi }d d d            n# 1 swxY w Y   d|v r_t-          |                    d          pi           }|                    d          |d         |d<   ||d<   |                    dd            t1          ||          }n'# t2          $ r}t5          ||           Y d }~nd }~ww xY wt7          t9          |                    }t;          |          }|                                }|rt;          |          }t1          ||          }t#          j        |          t>          |<   |
Wt#          j        |          }tA          |          }|rtA          ||           g |
||R t          |<   | s|cd d d            S nt                              |d            |cd d d            S # 1 swxY w Y   d S )Nr   managed_scoperH  )r   r   r  r  r  c              3   b   K   | ]*\  }}t           j                            |          |k    V  +d S r  )r   r8  r   r}  s      r$   r/  z$_load_config_impl.<locals>.<genexpr>  s9      KKda2:>>!$$)KKKKKKrZ   r   r   r  r  )!r  r  rI  r+   r   r,   r   r<  
hermes_clirQ  get_managed_dirr   r\   r   r\  r	  r&  r]  r^  rK  r9  rF  r2  rB  r  r   r6   r  r  r  load_managed_configr[   r  )r  r   r  r   user_sigrQ  managed_dirmanaged_cfg_pathmstmanaged_sig	cache_sigr  env_snapshotr5  r?  user_configagent_user_configrT  r   expandedmanaged_configmanaged_expandedcached_copys                          r$   r  r  ]  s'   	 i i%''{##	!!##B35>2:2NHH  	 	 	HHH	 	-,,,,,#3355<GQK-77T	!-=G"'')))4C<?K3?CK88VKK 	! 	! 	! KKK	!
 AA	>II F""A{1~{1~>III#''11)"7F2A2J)<S<S ),Fa6!99RLKKl6H6H6J6JKKKKK P3@Ot}VAY///fQi_i i i i i i i ib ~..;+888 :A"0"3"3"9rK: : : : : : : : : : : : : : : +--(,[__W-E-E-K(L(L%(,,[99A9D[9Q)+6+<K(OOK666$V[99 ; ; ;*;::::::::; 00KF0S0STT
#J// '::<< 	?/??"8-=>>H26-2I2I%h/  -11K,Z88L @!.,???+RY+R+R\+R+Rx( ! #"Ci i i i i i i i@# ""8T222 Si i i i i i i i i i i i i i i i i is   ,N"ANA'$N&A''&N*B98N9CNCCN+NI5G6*I56G:	:I5=G:	>A6I54N5
J?JNJC
N0NN!Nu-  
# ── Security ──────────────────────────────────────────────────────────
# Secret redaction is ON by default — strings that look like API keys,
# tokens, and passwords are masked in tool output, logs, and chat
# responses before the model or user ever sees them. Set redact_secrets
# to false to disable (e.g. when developing the redactor itself).
# tirith pre-exec scanning is enabled by default when the tirith binary
# is available. Configure via security.tirith_* keys or env vars
# (TIRITH_ENABLED, TIRITH_BIN, TIRITH_TIMEOUT, TIRITH_FAIL_OPEN).
#
# security:
#   redact_secrets: true
#   tirith_enabled: true
#   tirith_path: "tirith"
#   tirith_timeout: 5
#   tirith_fail_open: true
u@  
# ── Fallback Model ────────────────────────────────────────────────────
# Automatic provider failover when primary is unavailable.
# Uncomment and configure to enable. Triggers on rate limits (429),
# overload (529), service errors (503), or connection failures.
#
# Supported providers:
#   openrouter   (OPENROUTER_API_KEY)  — routes to any model
#   openai-codex (OAuth — hermes auth) — OpenAI Codex
#   nous         (OAuth — hermes auth) — Nous Portal
#   zai          (ZAI_API_KEY)         — Z.AI / GLM
#   kimi-coding  (KIMI_API_KEY)        — Kimi / Moonshot
#   kimi-coding-cn (KIMI_CN_API_KEY)   — Kimi / Moonshot (China)
#   minimax      (MINIMAX_API_KEY)     — MiniMax
#   minimax-cn   (MINIMAX_CN_API_KEY)  — MiniMax (China)
#   bedrock      (AWS IAM / boto3)     — AWS Bedrock (Converse API)
#
# For custom OpenAI-compatible endpoints, add base_url and key_env.
#
# fallback_model:
#   provider: openrouter
#   model: anthropic/claude-sonnet-4
u  
# ── Security ──────────────────────────────────────────────────────────
# Secret redaction is ON by default. Set to false to pass tool output,
# logs, and chat responses through unmodified (e.g. for redactor dev).
#
# security:
#   redact_secrets: true

# ── Fallback Model ────────────────────────────────────────────────────
# Automatic provider failover when primary is unavailable.
# Uncomment and configure to enable. Triggers on rate limits (429),
# overload (529), service errors (503), or connection failures.
#
# Supported providers:
#   openrouter   (OPENROUTER_API_KEY)  — routes to any model
#   openai-codex (OAuth — hermes auth) — OpenAI Codex
#   nous         (OAuth — hermes auth) — Nous Portal
#   zai          (ZAI_API_KEY)         — Z.AI / GLM
#   kimi-coding  (KIMI_API_KEY)        — Kimi / Moonshot
#   kimi-coding-cn (KIMI_CN_API_KEY)   — Kimi / Moonshot (China)
#   minimax      (MINIMAX_API_KEY)     — MiniMax
#   minimax-cn   (MINIMAX_CN_API_KEY)  — MiniMax (China)
#   bedrock      (AWS IAM / boto3)     — AWS Bedrock (Converse API)
#
# For custom OpenAI-compatible endpoints, add base_url and key_env.
#
# fallback_model:
#   provider: openrouter
#   model: anthropic/claude-sonnet-4
)strip_defaultsr  rc  c                   t           5  t                      rt          d           	 ddd           dS ddlm} |                                }|rut          t          j        |           |          \  } }|rNt          dt          |           dd                    t          |                     t          j                   dd	lm} t#                       t%                      }t'          |           t)                      }|rt+          |          nd}	t-          t/          |                     }
|
}t-          t/          t)                                          }|r6t1          ||t2                              t7          |                              }d
h}|	r|                    |	           |r|                    |           |rB|r@ddlm}  |t>          t6          t@          f         |          }tC          |tD          |          }g }|                    di           }|r|                    d          |#                    tH                     |                    di           }d}tK          |tL                    rtO          d |D                       }nLtK          |tP                    r7tS          |                    d          o|                    d                    }|s|#                    tT                      ||||rd                    |          nd           tW          |           t          j        |
          t2          t7          |          <   ddd           dS # 1 swxY w Y   dS )a{  Save configuration to ~/.hermes/config.yaml.


    Default values from ``DEFAULT_CONFIG`` are not written to disk unless
    the user explicitly set them (i.e. the path exists in the raw config
    before any normalisation).  This prevents config.yaml from being
    contaminated with schema defaults on every save, which makes future
    default changes invisible to users.
    zsave configurationNr   rP  zNote: zD managed setting(s) were not saved (managed by your administrator): rx  r*  r  r  )cast)r  r  r  r  Fc              3      K   | ]C}t          |t                    o)|                    d           o|                    d          V  DdS )r'  r  N)r0  r2  r   )r~  rT  s     r$   r/  zsave_config.<locals>.<genexpr>l  sN      gg_`jD11ZaeeJ6G6GZAEERYNNggggggrZ   r'  r  r   )extra_content),r  r   r-  rS  rQ  managed_config_keysr  r]  r^  r,  r\  r  r  r0   r1   r  r  r  rI  r
  rW  r  r  r  r  r[   r   r+   r  typingre  r   r   r  rK  r(  _SECURITY_COMMENTr0  r   r  r2  r  _FALLBACK_COMMENTrr  )r5  rc  r  rQ  managed_keys	_strippedr  r   _raw_for_pathsexplicit_raw_pathscurrent_normalizedr   raw_existingeffective_preserve_keys_castr6  secr  fb_is_valids                      r$   r   r     sT    
 U\ U\<< 	.///U\ U\ U\ U\ U\ U\ U\ U\ 	-,,,,,$88:: 	 24=3H3H, W WFI WS^^ W W8<		&BSBS8T8TW W   
 	,+++++%'',[999
 )**6DN">222$ 	
 88STZ8[8[\\'
12MoN_N_2`2`aa 	4-11#k2B2BCC J :N8N 	?#**+=>>> 	:#**=999 	5 	,,,,,,tCH~z::J.5  J nnZ,, 	,cgg.//7LL*+++^^,b11b$ 	GggdfgggggKKD!! 	Grvvj11EbffWooFFK 	,LL*+++,1;"''%...t	
 	
 	
 	

 	[!!!:>-HZ:[:[%c+&6&67kU\ U\ U\ U\ U\ U\ U\ U\ U\ U\ U\ U\ U\ U\ U\ U\ U\ U\s   M,L+M,,M03M0	raw_valuec                 `   |                                  }t          |          dk    r|d         |d         cxk    rdk    rn n|dd         }g }d}|t          |          k     r{||         }|dk    r@|dz   t          |          k     r*||dz            }|dv r|                    |           |dz  }a|                    |           |dz  }|t          |          k     {d                    |          S t          |          dk    r&|d         |d         cxk    rd	k    rn n
|dd         S |S )
z7Parse the small .env value subset Hermes writes itself.r  r   r\  "r  \>   rx  ry  r   rC  )r   r\  r(  r  )rv  rB  quotedr  r  chnext_chs          r$   _parse_env_valuer}  {  sT   OOE
5zzQ58uRy7777C77777qt#f++ooBTzza!ec&kk11 Q-k))MM'***FAMM"FA #f++oo wwv
5zzQ58uRy7777C77777QrT{LrZ   c                  h   t                      } 	 |                                 j        }|                                 j        }t	          |           ||f}n/# t
          $ r t	          |           ddf}Y nt          $ r d}Y nw xY w|&t          t          \  }}||k    rt          |          S i }| 	                                rddd}t          | fi |5 }|                                }	ddd           n# 1 swxY w Y   t          |	          }
|
D ]}|                                }|ru|                    d          s`d|v r\|                    d          r
|dd         }|                    d          \  }}}t!          |          ||                                <   ||t          |          fa|S )	u  Load environment variables from ~/.hermes/.env.

    Sanitizes lines before parsing so that corrupted files (e.g.
    concatenated KEY=VALUE pairs on a single line) are handled
    gracefully instead of producing mangled values such as duplicated
    bot tokens.  See #8908.

    The parsed dict is memoised keyed on the .env file mtime, because
    ``get_env_value()`` is called dozens-to-hundreds of times per
    interactive menu render (`hermes tools`, `hermes setup`, status
    panels). Sanitisation is O(lines × known-keys), so re-parsing the
    same file on every call was burning ~300ms of CPU per `hermes tools`
    menu paint on top of the OAuth-refresh slowness. The mtime check
    invalidates the cache when the user edits .env mid-process.
    N	utf-8-sigr   r   errorsr2  r1  zexport rz  )rL  r   st_mtimer   r+   r<  r   
_env_cacher2  r   r9  	readlines_sanitize_env_linesr   r:  r;  r}  )env_pathmtimesizer  
cached_keycached_varsenv_varsopen_kwr?  	raw_linesr  r@  r4   rA  rB  s                  r$   load_envr    s    " ~~H(}}&]]E40		 0 0 0]]D$/			   			 !7",
K""$$$!H @  +i@@(&&g&& 	&!I	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& $I.. 		@ 		@D::<<D @DOOC00 @SD[[ ??9-- $8D $s 3 3Q(8(?(?%h0
Os*   AA B3B BC55C9<C9r  c                  
    da dS )a  Clear the load_env() process-level memo.

    Writers that mutate .env (set_env_value, save_env, etc.) call this
    to guarantee the next load_env() sees their change even on
    filesystems with coarse mtime resolution. Reads invalidate naturally
    via the mtime/size check.
    N)r  r   rZ   r$   invalidate_env_cacher    s     JJJrZ   )z://r]  &c                 z     t           fdt          D                       rdS t          d  D                       S )a  True when ``value`` looks like a URL/query string or holds whitespace.

    Such a value is treated as one opaque secret. An embedded
    ``KNOWN_KEY=`` substring inside it (e.g. a webhook URL carrying a query
    parameter, or a proxy base URL with an embedded key) is part of the value,
    not the start of a second .env entry, so the concatenation splitter must
    not break on it. Plain token secrets (API keys) never contain these.
    c              3       K   | ]}|v V  	d S r  r   )r~  r  rB  s     r$   r/  z/_looks_like_structured_value.<locals>.<genexpr>  s'      
C
Cv6U?
C
C
C
C
C
CrZ   Tc              3   >   K   | ]}|                                 V  d S r  )isspace)r~  r{  s     r$   r/  z/_looks_like_structured_value.<locals>.<genexpr>  s*      ,,rzz||,,,,,,rZ   )r  _STRUCTURED_VALUE_MARKERSr?  s   `r$   _looks_like_structured_valuer    sM     
C
C
C
C)B
C
C
CCC t,,e,,,,,,rZ   r  c           	         t          t                                                    t          z  }g }| D ]}|                    d          }|                                r                    d          r|                    |dz              \g |D ]u}|dz   }                    |          }|dk    rS                    ||t          |          z   f                               ||t          |          z             }|dk    Svt          fdD                       d}g }	t                    dk    rId         dk    r=fd	t                    D             }	t          d
 |	dd         D                       }|r5|	D ]0}
|
                                }|r|                    |dz              1|                    dz              |S )a  Fix corrupted .env lines before reading or writing.

    Handles two known corruption patterns:
    1. Concatenated KEY=VALUE pairs on a single line (missing newline between
       entries, e.g. ``ANTHROPIC_API_KEY=sk-...OPENAI_BASE_URL=https://...``).
    2. Stale ``KEY=***`` placeholder entries left by incomplete setup runs.

    Uses a known-keys set (OPTIONAL_ENV_VARS + _EXTRA_ENV_KEYS) so we only
    split on real Hermes env var names, avoiding false positives from values
    that happen to contain uppercase text with ``=``.
    z
r2  r*   r1  r   c                 V    h | ]#\  t          fd D                       !$S )c              3   J   K   | ]\  }}|k    o|k    o	||ffk    V  d S r  r   )r~  s2e2rT  ss      r$   r/  z0_sanitize_env_lines.<locals>.<setcomp>.<genexpr>  sZ        B a:B!G:RQF(:     rZ   )r  )r~  rT  r  match_rangess    @@r$   rK  z&_sanitize_env_lines.<locals>.<setcomp>  sj     "
 "
 "
!Q     *    "
"
 "
 "
rZ   Fr  c                     g | ]=\  }}||d z   t                    k     r|d z            nt                             >S r  )r\  )r~  r  possplit_positionsr  s      r$   rO  z'_sanitize_env_lines.<locals>.<listcomp>,  sl        As ./!ec/6J6J.J.JOAE**PST\P]P]   rZ   c              3   j   K   | ].}t          |                    d d          d                    V  /dS )r1  r  N)r  r/  )r~  segs     r$   r/  z&_sanitize_env_lines.<locals>.<genexpr>7  sT       % % 131B1B11EFFF% % % % % %rZ   Nr\  )r  r)  r  _EXTRA_ENV_KEYSr  r   r:  r(  findr\  r  r  r	  )r  
known_keys	sanitizedr@  r   key_nameneedler9  split_into_entriessegmentsr  r8  r  r  r  s               @@@r$   r  r    s[    &++--..@JI <. <.kk&!!99;;  	8..s33 	S4Z((( /1" 	? 	?H^F--''C((##S#F*;$<===mmFC#f++,=>> (( ! "
 "
 "
 "
&"
 "
 "
   # !##(:a(?(?     (88	  H "% % %#CRC=% % % " "
  	. 2 2yy{{ 2$$TD[1112
 X_----rZ   c                  $   t                      } |                                 sdS ddd}ddi}t          | fi |5 }|                                }ddd           n# 1 swxY w Y   t	          |          }||k    rdS t          t          |          t          |          z
            }|dk    rVt          d t          ||          D                       }|t          t          |          t          |          z
            z  }t          j
        t          | j                  d	d
          \  }}	 t          j        |dfi |5 }|                    |           |                                 t          j        |                                           ddd           n# 1 swxY w Y   t'          ||            n5# t(          $ r( 	 t          j        |           n# t,          $ r Y nw xY w w xY wt/          |            t1                       |S )zRead, sanitize, and rewrite ~/.hermes/.env in place.

    Returns the number of lines that were fixed (concatenation splits +
    placeholder removals).  Returns 0 when no changes are needed.
    r   r  r   r  r   r   Nc              3   ,   K   | ]\  }}||k    d V  dS )r  Nr   )r~  abs      r$   r/  z$sanitize_env_file.<locals>.<genexpr>`  s*      KK$!QAFFAFFFFKKrZ   .tmp.env_dirrt  r  r  )rL  r   r9  r  r  absr\  sumziptempfilemkstempr+   r   r   fdopen
writelinesr3   fsyncfilenorE  BaseExceptionunlinkr   rr  r  )	r  read_kwwrite_kwr?  original_linesr  rg  fdtmp_paths	            r$   rV  rV  G  s    ~~H?? q&)<<GG$H	h	"	"'	"	" 'a' ' ' ' ' ' ' ' ' ' ' ' ' ' ' $N33IN""q I^!4!4455EzzKK#ni"@"@KKKKKS^^c.&9&99:::#HO(<(<VT[\\\LBYr3++(++ 	!qLL###GGIIIHQXXZZ   	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	x****   	Ih 	 	 	D	 Lsf   AA #A 3F> AF"F> "F&&F> )F&*F> >
G0	GG0
G+(G0*G++G0c                    	 |                     d           |S # t          $ r Y nw xY wg }t          |          D ]E\  }}t          |          dk    r-|                    d| d|dt          |          dd           F|                     dd	                              d          }t          d
|  dd                    d |dd         D                       z   t          |          dk    rdndz   dz   t          j
                   |S )u1  Warn and strip non-ASCII characters from credential values.

    API keys and tokens must be pure ASCII — they are sent as HTTP header
    values which httpx/httpcore encode as ASCII.  Non-ASCII characters
    (commonly introduced by copy-pasting from rich-text editors or PDFs
    that substitute lookalike Unicode glyphs for ASCII letters) cause
    ``UnicodeEncodeError: 'ascii' codec can't encode character`` at
    request time.

    Returns the sanitized (ASCII-only) value.  Prints a warning if any
    non-ASCII characters were found and removed.
    ascii   z  position r(   z (U+04Xr  ignore)r  z
  Warning: z contains non-ASCII characters that will break API requests.
  This usually happens when copy-pasting from a PDF, rich-text editor,
  or web page that substitutes lookalike Unicode glyphs for ASCII letters.

r*   c              3       K   | ]	}d | V  
dS )r  Nr   )r~  r@  s     r$   r/  z._check_non_ascii_credential.<locals>.<genexpr>  s(      ::DKKK::::::rZ   Nr  z
  ... and morer   z

  The non-ASCII characters have been stripped automatically.
  If authentication fails, re-copy the key from the provider's dashboard.
r*  )encodeUnicodeEncodeErrorr  ordr(  decoder,  r  r\  r0   r1   )r4   rB  	bad_charsr  r{  r  s         r$   _check_non_ascii_credentialr  u  sc   W    I5!! J J2r77S==H1HHHH#b''HHHHIIIWX66==gFFI		 	 	 	 ))::IbqbM:::
:
:		;
 "%Y!!3!3	=V	V Z
 
 
 
 s    
&&c                     | dk    r| S d| v pd| v pd| v p| |                                  k    }|s| S |                     dd                              dd          }d| dS )zDQuote .env values containing characters with special dotenv meaning.r   r2  rx  rC  ry  z\\z\")r   r   )rB  needs_quotingescapeds      r$   _quote_env_valuer    s    {{u 	"%<	"%<	" EKKMM!	   mmD&))11#u==Gw>>>rZ   c                 n   t                      rt          d|             dS ddlm} |                    |           rA|                                }|r|dz  nd}t          d|  d| d	t          j        
           dS t          
                    |           st          d|           t          |            |                    dd                              dd          }t          | |          }t                       t!                      }ddd}ddi}g }|                                rHt%          |fi |5 }	|	                                }ddd           n# 1 swxY w Y   t)          |          }t+          |          }
d}t-          |          D ]>\  }}|                                                    |  d          r|  d|
 d||<   d} n?|sH|r+|d                             d          s|dxx         dz  cc<   |                    |  d|
 d           t7          j        t;          |j                  dd          \  }}d}|                                r=	 t?          j         |                                j!                  }n# tD          $ r Y nw xY w	 tG          j$        |dfi |5 }	|	%                    |           |	&                                 tG          j'        |	(                                           ddd           n# 1 swxY w Y   tS          ||           |'	 tG          j*        ||           n# tD          $ r Y nw xY wtW          |           n5# tX          $ r( 	 tG          j-        |           n# tD          $ r Y nw xY w w xY w|tF          j.        | <   t_                       dS )z)Save or update a value in ~/.hermes/.env.zset Nr   rP  rK  the managed scopezCannot set ': it is managed by your administrator () and cannot be changed.r*  #Invalid environment variable name: r*   r   r  r   r  r   r   Fr1  Tr\  r  r  r  r  )0r   r-  rS  rQ  is_env_managedrT  r,  r0   r1   _ENV_VAR_NAME_REmatchrX   rY   r   r  r  rL  r   r9  r  r  r  r  r   r:  endswithr(  r  r  r+   r   r   S_IMODEst_moder   r   r  r  r3   r  r  rE  rd  rr  r  r  r8  r  )r4   rB  rQ  rW  srcr  r  r  r  r?  serialized_valuefoundr  r@  r  r  original_modes                    r$   rX  rX    s   || lSll### )(((((##C(( #3355(3L{V##9L&# & &c & & &	
 	
 	
 	

 	!!#&& HFsFFGGGs###MM$##++D"55E'U33E~~H ')<<GG$HE +(&&g&& 	"!KKMME	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" $E**'.. EU##  4::<<""c999-- 	44 0444E!HEE	
  4 	r++D11 	"IIIIII22.222333#HO(<(<VT[\\\LBM 	 L)@AAMM 	 	 	D	Yr3++(++ 	!qLLGGIIIHQXXZZ   	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	x*** $=1111    """   	Ih 	 	 	D	 BJsOs   <EE!$E!#+J 
JJ M# 3ALM# LM# LM# -M M# 
MM# MM# #
N.NN
NNNNc                     t                      rt          d             dS ddlm} |                               rA|                                }|r|dz  nd}t          d  d| d	t          j        
           dS t          
                               st          d           t                      }|                                s"t          j                             d           dS ddd}ddi}t#          |fi |5 }|                                }ddd           n# 1 swxY w Y   t'          |          } fd|D             }	t)          |	          t)          |          k     }
|
rdt+          j        t/          |j                  dd          \  }}d}	 t3          j        |                                j                  }n# t8          $ r Y nw xY w	 t          j        |dfi |5 }|                    |	           |                                 t          j         |!                                           ddd           n# 1 swxY w Y   tE          ||           |'	 t          j#        ||           n# t8          $ r Y nw xY wtI          |           n5# tJ          $ r( 	 t          j&        |           n# t8          $ r Y nw xY w w xY wt          j                             d           tO                       |
S )zzRemove a key from ~/.hermes/.env and os.environ.

    Returns True if the key was found and removed, False otherwise.
    zremove Fr   rP  rK  r  zCannot remove r  r  r*  r  Nr  r   r  r   r   c                 h    g | ].}|                                                      d           ,|/S )r1  )r   r:  )r~  r@  r4   s     r$   rO  z$remove_env_value.<locals>.<listcomp>(  s:    RRR$tzz||/F/F#yyy/Q/QRRRRrZ   r  r  r  r  )(r   r-  rS  rQ  r  rT  r,  r0   r1   r  r  rX   rL  r   r   r8  rB  r9  r  r  r\  r  r  r+   r   r   r  r  r   r  r  r3   r  r  rE  rd  rr  r  r  r  )r4   rQ  rW  r  r  r  r  r?  r  	new_linesr  r  r  r  s   `             r$   remove_env_valuer    s   
 || ooo&&&u((((((##C(( #3355(3L{V##9L&S & & & & &	
 	
 	
 	

 u!!#&& HFsFFGGG~~H?? 

sD!!!u&)<<GG$H	h	"	"'	"	" a              &&ERRRR%RRRI	NNSZZ'E 'C,@,@X_```H	 L)@AAMM 	 	 	D		2s//h// %1Y'''			$$$% % % % % % % % % % % % % % % 8X... (HX}5555   D X&&& 	 	 		(####   	 JNN3Ls   D''D+.D+!+G 
GGJ! 1AIJ! IJ! IJ! +J  J! 
JJ! JJ! !
K,K K
KKKKc                 H    |pt           } |d|             |dd           dS )zBPersist an Anthropic OAuth/setup token and clear the API-key slot.rn   r}   r   NrX  rB  save_fnwriters      r$   save_anthropic_oauth_tokenr  O  s8    &F
Fe$$$
F#####rZ   c                 H    | pt           } |dd            |dd           dS )zHUse Claude Code's own credential files instead of persisting env tokens.rn   r   r}   Nr  )r  r  s     r$   %use_anthropic_claude_code_credentialsr  V  s8    &F
Fb!!!
F#####rZ   c                 H    |pt           } |d|             |dd           dS )zBPersist an Anthropic API key and clear the OAuth/setup-token slot.r}   rn   r   Nr  r  s      r$   save_anthropic_api_keyr  ]  s8    &F
F&&&
Fb!!!!!rZ   c                 .    t          | |           d| ddS )NTF)success	stored_as	validatedr  )r4   rB  s     r$   save_env_value_securer  d  s*    3  rZ   c                  v   t                      } t          t                                                    t          z  }d}|                                 D ]<\  }}t          j                            |          |k    r|t          j        |<   |dz  }=|D ]&}|| vr |t          j        v rt          j        |= |dz  }'|S )u&  Re-read ~/.hermes/.env into os.environ. Returns count of vars updated.

    Adds/updates vars that changed and removes vars that were deleted from
    the .env file (but only vars known to Hermes — OPTIONAL_ENV_VARS and
    _EXTRA_ENV_KEYS — to avoid clobbering unrelated environment).
    r   r  )	r  r  r)  r  r  r&  r   r8  r   )r  r  countr4   rB  s        r$   
reload_envr  n  s     zzH&++--..@JEnn&&  
U:>>#%''#BJsOQJE  h3"*#4#4
3QJELrZ   c                     | t           j        v rt           j        |          S t                      }|                    |           S )z/Get a value from ~/.hermes/.env or environment.)r   r8  r  r   )r4   r  s     r$   r'  r'    s:     bjz# zzH<<rZ   c                     t                      }|                    |           }|r|S 	 ddlm}  ||           S # t          $ r" t
          j                            |           cY S w xY w)u  Resolve a credential env value, preferring ``~/.hermes/.env`` over ``os.environ``.

    Used for Hermes-managed credentials where a deliberate edit to ``.env``
    must take precedence over a stale value inherited from the parent shell
    (Codex CLI, test scripts, login profile exports). Without this, rotating
    a key in ``.env`` mid-session leaves callers serving the stale shell
    value and produces persistent 401s.

    The ``os.environ`` fallback routes through ``secret_scope.get_secret`` so
    that, under an active profile scope (multiplexed gateway turn), this read
    is scope-checked rather than leaking another profile's raw ``os.environ``
    value — matching the credential-pool seeding path's behaviour.
    r   )
get_secret)r  r   agent.secret_scoper  r   r   r8  )r4   r  val_get_secrets       r$   get_env_value_prefer_dotenvr    s     zzH
,,s

C
 
#@@@@@@{3 # # #z~~c"""""#s   : )A&%A&c                 X    ddl m}  || t          dt          j                            S )u   Redact an API key for display.

    Thin wrapper over :func:`agent.redact.mask_secret` — preserves the
    "(not set)" placeholder in dim color for the empty case.
    r   mask_secret	(not set))empty)agent.redactr  r   r   DIM)r4   r  s     r$   
redact_keyr	    s7     )(((((;s%VZ"@"@AAAArZ   >   jwtr4   authtokenapikeybearerpasswdr  r)  id_tokenr  private_keyaccess_tokenauthorizationclient_secretrefresh_token_depthc                    ddl m} dk    r| S t          | t                    ri }|                                 D ]q\  }}t          |t
                    rA|                                t          v r&t          |t
                    r|r ||          ||<   [t          |dz             ||<   r|S t          | t                    rfd| D             S | S )uf  Return a copy of ``value`` with credential-shaped keys masked for display.

    Recursively walks dicts/lists and replaces the value of any key in
    ``_SECRET_CONFIG_KEYS`` (case-insensitive) with a masked form via
    :func:`agent.redact.mask_secret`. Non-secret keys and scalar values pass
    through unchanged. Use this before ``print``-ing any config sub-tree that
    might carry a custom-provider ``api_key`` — ``print`` bypasses the logging
    redactor, and opaque tokens (e.g. Cloudflare ``cfut_...``) don't match the
    vendor-prefix regexes either, so structural key-name masking is required.
    r   r  r  r  c                 6    g | ]}t          |d z             S r  )redact_config_value)r~  r  r  s     r$   rO  z'redact_config_value.<locals>.<listcomp>  s(    BBBq#Avz22BBBrZ   )
r  r  r0  r2  r&  r+   r   _SECRET_CONFIG_KEYSr  r   )rB  r  r  r  r  r  s    `    r$   r  r    s     )((((( {{% KKMM 	< 	<DAq!S!! <aggii3F&F&F:VWY\K]K]&Fbc&F$QA,Q
;;A
% CBBBBEBBBBLrZ   c                  d   t                      } t                       t          t          dt          j                             t          t          dt          j                             t          t          dt          j                             ddlm} |                                }|                                }|s|r|	                                }t                       t          t          d| dt          j
        t          j                             |rJt          t          dd	                    t          |                     t          j
                             |rJt          t          d
d	                    t          |                     t          j
                             t                       t          t          dt          j        t          j                             t          dt                                  t          dt                                  t          dt!                                  t                       t          t          dt          j        t          j                             g d}|D ]7\  }}t#          |          }t          d|ddt%          |                      8ddlm}	  |	            }
t          ddddt%          |
                      t                       t          t          dt          j        t          j                             t          dt+          |                     dd                                |                     di                               dt.          d         d                   }t          d|            	 t1                                          d          }|ot3          |                                          t3          |                                          k    r+t          t          d| d t          j
                             n# t6          $ r Y nw xY wt                       t          t          d!t          j        t          j                             |                     d"i           }t          d#|                    d$          pd%            t          d&|                    d'd(          rd)nd*            t          d+|                    d,d(          rd)nd*            t9          |                    d-i           t:                    r|                    d-i           ni }|                    d.d/          }|                    d0d/          }t          d1| d2| d3           t                       t          t          d4t          j        t          j                             |                     d5i           }t          d6|                    d7d8                      t          d9|                    d:d;                      t          d<|                    d=d>           d?           |                    d7          d@k    r(t          dA|                    dBdC                      nj|                    d7          dDk    r(t          dE|                    dFdG                      n)|                    d7          dHk    rLt          dI|                    dJdC                      t#          dK          }t          dL|rdMndN            n|                    d7          dOk    rLt          dP|                    dQdC                      t#          dR          }t          dS|rdMndN            n_|                    d7          dTk    rFt#          dU          }t#          dV          }t          dW|pdN            t          dX|pdN            t                       t          t          dYt          j        t          j                             |                     dZd[          }|rt          d\|            n*t          d\t          d]t          j                              t                       t          t          d^t          j        t          j                             |                     d_i           }|                    d`da          }t          db|rdcndd            |r(t          de|                    dfdg          dhz  didj           t          dk|                    dldm          dhz  didn           t          do|                    dpdq           dr           t          ds|                    dtdu           dv           |                     dwi                               d_i           }|                    dd[          pdx}t          d|            |                    dydz          }|r|dzk    rt          d{|            |                     dwi           }|                    d|i           |                    d}i           d~}t?          d |                                 D                       }|rt                       t          t          dt          j        t          j                             |!                                D ]\  }} |                     dydz          }!|                     dd[          }"|!dzk    s|"rId|! g}#|"r|#"                    d|"            t          d|ddd	                    |#                      t                       t          t          dt          j        t          j                             t#          d          }$t#          d          }%t          d|$rdMnt          dt          j                              t          d|%rdMnt          dt          j                              	 ddl#m$}&m%}'  |&            }(|(r |'|(          })t                       t          t          dt          j        t          j                             |(D ]}*|*d         }+|)                    |+d[          }|*                    dd[          },|rt3          |          nt          dNt          j                  }-t          d|+dd|- dt          d|, dt          j                              n# t6          $ r Y nw xY wt                       t          t          dt          j                             t          t          dt          j                             t          t          dt          j                             t          t          dt          j                             t                       dS )zDisplay current configuration.u   ┌─────────────────────────────────────────────────────────┐u@   │              ⚕ Hermes Configuration                    │u   └─────────────────────────────────────────────────────────┘r   rP  u7     ⚷ Some settings are managed by your administrator (z) and cannot be changedz    Managed config keys: rx  z    Managed env keys: u	   ◆ Pathsz  Config:       z  Secrets:      z  Install:      u   ◆ API Keys)	)r  
OpenRouter)r  zOpenAI (STT/TTS))r  Exa)r  Parallel)r  	Firecrawl)r  Tavily)r  Browserbase)r  zBrowser Use)r  FALr  z<14r   )get_anthropic_key	Anthropicu	   ◆ Modelz  Model:        r  znot setr  r  z  Max turns:    HERMES_MAX_ITERATIONSNu9                   ⚠ .env has stale HERMES_MAX_ITERATIONS=z& (run 'hermes doctor --fix' to remove)u   ◆ Displayr;  z  Personality:  r=  nonez  Reasoning:    rN  FrQ  r  z  Bell:         rM  rc  rd  r  re  z  User preview: first z line(s), last z line(s)u   ◆ Terminalr  z  Backend:      r3  r  z  Working dir:  r  r)   z  Timeout:      r  r6  r  r   z  Docker image: r  r  singularityz  Image:        r  r  modalz  Modal image:  r  MODAL_TOKEN_IDz  Modal token:  
configuredr  daytonaz  Daytona image: r  DAYTONA_API_KEYz  API key:      sshr&  r(  z  SSH host:     z  SSH user:     u   ◆ Timezoner  r   z  Timezone:     r  u   ◆ Context Compressionr  r  Tz  Enabled:      r   r  z  Threshold:    r  r  d   z.0f%z  Target ratio: r  r  z% of threshold preservedz  Protect last: r  r  z	 messagesz  Protect first: r
  r  z non-system head messagesr$  z(auto)r'  r  z  Provider:     r&  r,  )VisionzWeb extractc              3   v   K   | ]4}|                     d d          dk    p|                     dd          V  5dS )r'  r  r  r   NrM  )r~  ts     r$   r/  zshow_config.<locals>.<genexpr>  s\         	
j&!!V+AquuWb/A/A     rZ   u    ◆ Auxiliary Models (overrides)r5  r4  12su   ◆ Messaging Platformsr  r  z  Telegram:     znot configuredz  Discord:      )rN  resolve_skill_config_valuesu   ◆ Skill Settingsr4   r  z<20s[]u   ────────────────────────────────────────────────────────────z+  hermes config edit     # Edit config filez!  hermes config set <key> <value>z+  hermes setup           # Run setup wizard)&rJ  r,  r   r   CYANrS  rQ  rh  load_managed_envrT  YELLOWBOLDr  r  rI  rL  rN  r'  r	  hermes_cli.authr#  r  r   rK  r  r+   r   r   r0  r2  r  r  r  r&  r(  rP  rN  r4  ).r5  rQ  _managed_keys_managed_env_managed_dirr  env_keyr   rB  r#  anthropic_value_cfg_max_turns
_env_ghostr;  ump	ump_firstump_lastr  modal_tokendaytona_keyr%  r'  tzr  r  	_aux_comp_smcomp_providerr$  	aux_taskshas_overrideslabeltask_cfgprovmdlr6  telegram_tokendiscord_tokenrN  r4  
skill_varsresolvedrU  r4   r  display_vals.                                                 r$   show_configrW    s   ]]F	GGG	%  D  FL  FQ  R  R  S  S  S	%RTZT_
`
`aaa	%  D  FL  FQ  R  R  S  S  S )(((((!5577M 1133L  $4466e%l % % %MK	
 
 	 	 	  	%NDIIf]6K6K,L,LNN      	%J6,3G3G)H)HJJ     
GGG	%V[&+
6
6777	
0_..
0
0111	
-\^^
-
-...	
1-//
1
1222 
GGG	%V[
9
9:::
 
 
D  3 3g&&14111j//112222111111''))O	
>{
>
>
>O!<!<
>
>??? 
GGG	%V[&+
6
6777	
R0GY1O1OPP
R
RSSSZZ,,00nW>UVa>bccN	
-^
-
-...	ZZ^^$;<<
!c*oo&;&;&=&=^ATATAZAZA\A\&\&\%9J 9 9 9    
     
GGG	%v{FK
8
8999jjB''G	
CW[[77A6
C
CDDD	
VW[[1A5%I%ITTTu
V
VWWW	
XW[[1CU%K%KVTTQV
X
XYYY5?Lbdf@g@gim5n5n
v'++,b
1
1
1tvCq))Iww|Q''H	
O9
O
OX
O
O
OPPP 
GGG	%V[
9
9:::zz*b))H	
?X\\)W==
?
?@@@	
7X\\%55
7
7888	
;X\\)R88
;
;
;<<<||I(**mn>j!k!kmmnnnn	i	 	 M	1	1{.ACx!y!y{{||||	i	 	 G	+	+lm=i!j!jllmmm#$455O!M+OOPPPP	i	 	 I	-	-o(,,@l"m"mooppp#$566O!M+OOPPPP	i	 	 E	)	) !455 !455:!8[::;;;:!8[::;;; 
GGG	%V[
9
9:::	J	#	#B	 H%%%&&&&F'7!D!DFFGGG 
GGG	%)6;
D
DEEE**]B//Kooi..G	
9g7UU4
9
9::: 
6Pd!C!Cc!IPPPPQQQj!F!F!LjjjjkkkQ1A2!F!FQQQRRRb+//2CQ"G"GbbbcccJJ{B//33M2FF	mmGR((4H&&&'''!j&99 	6]f444]44555 

;++I }}Xr22 }}]B77 I   !!##    M  
<e6V[QQRRR(00 	< 	<OE8<<
F33D,,w++Cv~~~+T++, 1LL#000:5:::		%(8(8::;;; 
GGG	%)6;
D
DEEE"#788N!"566M	
f^d\\GWY_YcAdAd
f
fggg	
e]c\\FVX^Xb@c@c
e
efffaaaaaaaa3355
 		]22:>>HGGG%,fk6;GGHHH! ] ]%j S"-- WWWb11
,1Uc%jjju[&*7U7U[3[[[k[[U;Lz;L;L;Lfj5Y5Y[[\\\\    
GGG	%
FJ
'
'(((	%=vz
J
JKKK	%3VZ
@
@AAA	%=vz
J
JKKK	GGGGGs&    BQ 
Q Q /C8u( (
u54u5c                  R   t                      rt          d           dS t                      } |                                 s(t	          t
          d           t          d|             t          j        d          pt          j        d          }|s5ddl	}ddl
}|j        d	k    rg d
}ng d}|D ]} |j        |          r|} n|s#t          d           t          d|             dS t          d|  d| d           t          j        |t          |           g           dS )z"Open config file in user's editor.zedit configurationNF)rc  zCreated r;   r<   r   rP  )notepadrh  vimvinano)r\  rZ  r[  rh  rY  z#No editor found. Config file is at:r  zOpening  in z...)r   r-  rI  r   r   rK  r,  r   r   r   r0   rT  r  
subprocessrunr+   )r   editorr   _sys
candidatescmds         r$   edit_configrd    s}   || *+++!##K  (N59999&&&''' Yx  7BIh$7$7F 
 	=G##AAAJJAAAJ 	 	Cv|C     3444 ;  !!!	
1[
1
1f
1
1
1222NFC,,-.....rZ   c                    t                      rt          d           dS ddlm} |                    |           rS|                                }|r|dz  nd}t          d|  d| d	t          j        
           t          j	        d           g d}| 
                                |v sN| 
                                                    d          s'| 
                                                    d          rEt          | 
                                |           t          d|  dt                                  dS t                      }t!          |           i }|                                rN	 t%          |d          5 }t'          |          pi }ddd           n# 1 swxY w Y   n# t(          $ r i }Y nw xY w|                                dv rd}nu|                                dv rd}n\|                                rt/          |          }n8|                    ddd                                          rt3          |          }t5          || |           |                                                                 }	|	dv r t9          |          }d} t          d           t;                       ddlm}
  |
||d           tA          |           }|r#| dk    rt          |tC          |                     | "                    dd          d                                         }|tF          v r)tI          |tJ                    r|rdd l&m'}  ||          }n|}t          d|  d!| d|            dS )"zSet a configuration value.zset configuration valuesNr   rP  rH  r  zCannot set 'z(': it is managed by your administrator (zA) and cannot be changed. Contact your administrator to modify it.r*  r  )r  rk   r}   r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r&  r(  rz   r  r  r  r  r  )_API_KEY_TOKENTERMINAL_SSHu   ✓ Set r]  r   r   >   rQ  r   r   T>   r  r  r  Fr)   r   )zmodel.api_baser  zmodel.base_urlu<     (note: 'api_base' is an alias — saved as model.base_url)r  )	sort_keyszterminal.cwdr\  r  rU  )(r   r-  rS  rQ  is_key_managedrT  r,  r0   r1   exitupperr  r:  rX  rL  rI  r
  r   r9  rF  r   r   isdigitrU  r   r  r;  r   r  r  r  r  rC  r@  rsplitr  r0  r+   r  r  )r4   rB  rQ  rW  r  api_keysr   r]  r?  _alias_normr  rL  	_leaf_keyr  _display_values                  r$   set_config_valuers    s&   || 0111 )(((((##C(( #3355/:S{]**@SO3 O O O O O	
 	
 	
 	

 	
 
 
H yy{{h#))++"6"67M"N"NRUR[R[R]R]RhRhiwRxRxsyy{{E***222,..22333
 "##K(555K 	kG444 6,Q//526 6 6 6 6 6 6 6 6 6 6 6 6 6 6 	 	 	KKK	 {{}}---	0	0	0	 E

	sB	"	"	*	*	,	, eS%(((
 ))++##%%K4440==LMMM''''''k;%@@@@ .c22G <3.((w 3E : :;;; 

3""2&,,..I'''Juc,B,B'u',,,,,,$U++	
>S
>
>^
>
>
>
>?????s6   /F*  FF* F""F* %F"&F* *F98F9c           	         t          | dd          }||dk    rt                       dS |dk    rt                       dS |dk    rt          | dd          }t          | dd          }|r|mt          d           t                       t          d	           t          d
           t          d           t          d           t	          j        d           t          ||           dS |dk    rt          t                                 dS |dk    rt          t                                 dS |dk    rt                       t          t          dt          j        t          j                             t                       t          d          }t                      }t                      \  }}|s?|s=||k    r7t          t          dt          j                             t                       dS ||k     rt          d| d|            |r t          dt#          |           d           d |D             }d |D             }	|r=t          dt#          |           d           |D ]}
t          d|
d                     |	rzt          dt#          |	           d            |	D ]W}
|
                    d!g           }|r!d"d#                    |dd$                    d%nd&}t          d|
d          |            Xt                       t)          d'd(          }t                       |d)         s|d*         r't          t          d+t          j                             |d,         rCt                       |d,         D ],}t          t          d-| t          j                             -t                       dS |d.k    rt                       t          t          d/t          j        t          j                             t                       t                      \  }}||k    rt          d| d0           n.t          t          d| d| d1t          j                             t                       t          t          d2t          j                             t,          D ]O}t/          |          rt          d3|            $t          t          d4| d5t          j                             Pt                       t          t          d6t          j                             t2                                          D ]\  }}t/          |          rt          d3|            '|                    d!g           }|r dd#                    |dd$                    nd&}t          t          d7| | t          j                             t                      }|rUt                       t          t          d8t#          |           d9t          j                             t          d:           t                       dS t          d;|            t                       t          d<           t          d=           t          d>           t          d?           t          d@           t          dA           t          dB           t          dC           t	          j        d           dS )DzHandle config subcommands.config_commandNshoweditr  r4   rB  z&Usage: hermes config set <key> <value>z	Examples:z3  hermes config set model anthropic/claude-sonnet-4z+  hermes config set terminal.backend dockerz0  hermes config set OPENROUTER_API_KEY sk-or-...r  r  zenv-pathmigrateu*   🔄 Checking configuration for updates...FrH  u    ✓ Configuration is up to date!z  Config version: rG  rQ  z1 new config option(s) will be added with defaultsc                 <    g | ]}|                     d           |S )r$  rM  rJ  s     r$   rO  z"config_command.<locals>.<listcomp>  s)    KKK!aeeM6J6JKAKKKrZ   c                 f    g | ].}|                     d           |                     d          ,|/S )r$  r  rM  rJ  s     r$   rO  z"config_command.<locals>.<listcomp>  sN     
 
 
55''
01j0A0A

 
 
rZ   u   
  ⚠️  z required API key(s) missing:u	        • r   u   
  ℹ️  z$ optional API key(s) not configured:r6  z (enables: rx  r  r  r   T)r  r  r  r  u   ✓ Configuration updated!r  u
     ⚠️  checku   📋 Configuration Statusu    ✓z (update available)z  Required:u       ✓ u       ✗ z
 (missing)z  Optional:u       ○ r  z new config option(s) availablez+    Run 'hermes config migrate' to add themzUnknown config command: zAvailable commands:z4  hermes config           Show current configurationz/  hermes config edit      Open config in editorz6  hermes config set <key> <value>   Set a config valuez;  hermes config check     Check for missing/outdated configz8  hermes config migrate   Update config with new optionsz/  hermes config path      Show config file pathz-  hermes config env-path  Show .env file path)getattrrW  rd  r,  r0   rk  rs  rI  rL  r   r   r7  r:  r,  rL  r  GREENr\  r   r  r  r9  r%  r'  REDr)  r&  r  )r[  subcmdr4   rB  r  r  rh  ri  required_missingoptional_missingrU  r6  	tools_strrf  r/   r+  r>  s                    r$   ru  ru  M  sA   T+T22F~6))	6			5dE4((gt,, 	em:;;;GGG+GHHH?@@@DEEEHQKKKe$$$$$	6		o     	:		lnn	9		e@&+v{[[\\\ +???244"6"8"8Z 	> 	kZ6O6O%:FLIIJJJGGGF ##E{EEEEFFF 	a_^,,___```KK{KKK
 
"
 
 

  	1U%5!6!6UUUVVV' 1 1/#f+//0000 	<\%5!6!6\\\]]]' < <,,EJRA$))E"1"I*>*>AAAAPR	:#f+:y::;;;; !T???; 	E7>#: 	E%4flCCDDD: 	DGGG":. D De222FMBBCCCC	7		e/fkJJKKK"6"8"8Z*$$8{8889999%^[^^z^^^`f`mnnoooeM6;//000) 	J 	JHX&& J+++,,,,e;x;;;VZHHIIIIeM6;//000/5577 	K 	KNHdX&& K+++,,,,"-->CK:DIIeBQBi$8$8:::	e<x<<<fjIIJJJJ244 	AGGG%QS00QQQSYS`aabbb?@@@ 	111222#$$$DEEE?@@@FGGGKLLLHIII?@@@=>>>rZ   c            	      ~   t           rdS da 	 ddlm}   |             D ]}|j        dvr|j        D ]z}|t
          v r|                    d           o|                    d           }|j        p|j         d|rd	nd
 |j        p|j         d|rd	nd |j	        pd|dddt
          |<   {dS # t          $ r Y dS w xY w)u   Populate OPTIONAL_ENV_VARS from provider profiles not already listed.

    Called once at module load time. Idempotent — repeated calls are no-ops.
    NTr   )list_providers>   r)  	_BASE_URL_URLr   zAPI keyzbase URL overridez"base URL (leave empty for default)r'  r  )_profile_env_vars_injectedr  r  	auth_typer  r)  r  display_namer   
signup_urlr   )r  _pp_var_is_keys       r$   _inject_profile_env_varsr    sO    " !%,,,,,,!>## 	 	C}L00  ,,,"mmK888VvAVAV=V&)&6&B#(#t#tRYErYY_r#t#t!$!1!=SX  A  AW@~		Z~  A  A>1T ' * $+ +!$''		 	    s   BB. .
B<;B<c            	         t           rdS da 	 ddl} t          t                                                    j        d         }|dz  dz  }|                                sdS |                                D ]^}|                                s|dz  }|                                s|dz  }|                                sK	 t          |d	d
          5 }t          |          pi }ddd           n# 1 swxY w Y   n# t          $ r Y w xY w|                    d          p|                    d          p|j        }t          |                    d          pg           }|                    |                    d          pg            |D ]C}	t!          |	t"                    r|	}
i }n6t!          |	t$                    r |	                    d          r|	d         }
|	}nS|
t&          v r]|
                                t+          |                    d          p|                    d                    }|s2|                    d          durt-          fddD                       }|                    d          p| d|                    d          p|
|                    d          pd||                    d          pddt&          |
<   E`dS # t          $ r Y dS w xY w)u   Populate OPTIONAL_ENV_VARS from bundled platform plugin manifests.

    Called once at module load time. Idempotent — repeated calls are no-ops.
    Failures are swallowed so a malformed plugin.yaml can't break CLI import.
    NTr   r  r7  ry  r8  r9  r0  r   r   rN  r   requires_envoptional_envr  r  Fc              3   B   K   | ]}                     |          V  d S r  )r  )r~  suf
name_uppers     r$   r/  z3_inject_platform_plugin_env_vars.<locals>.<genexpr>K   sE       $ $ #++C00$ $ $ $ $ $rZ   )rg  _SECRET_KEY	_PASSWORD_JSONr  z configurationr  rA  r  r   r  )"_platform_plugin_env_vars_injectedyamlr   r   r   r  r   r[  r   r9  rF  r   r   r   r   re  r0  r+   r2  r)  rl  r  r  )r  	repo_rootplatforms_dirr  manifest_pathr?  r  rN  entriesra  r   meta	is_secretr  s                @r$    _inject_platform_plugin_env_varsr     s    * )-&9 NN**,,4Q7	!I-;##%% 	F"**,, .	 .	E<<>> !M1M '')) 5 % 4 '')) -w??? 71-a006BH7 7 7 7 7 7 7 7 7 7 7 7 7 7 7   LL))OX\\&-A-AOUZE8<<77=2>>GNN8<<77=2>>>   eS))  D!#DDt,, 61B1B  =D DD,,, "ZZ\\
 *!5!5!K(9K9KLL	  *)=)=)F)F # $ $ $ $#V$ $ $ ! !I // 4#333"hhx008D88E??2d ) $ 4 4 C	+ 	+!$'')#.	 .	^    sb   AK !AK C=C1%C=1C5	5C=8C5	9C=<K =
D
K 	D

GK 
K%$K%r  )r#  )r(  )r   N)FrQ  )TF)r   )r  r]  r=  r;  r   rT  r  r   r   r^  r0   r  	threadingr   dataclassesr   pathlibr   ri  r   r   r   r   r	   r
   hermes_cli.secret_promptr   rQ  r4  r.   r  r   r  r%   r   r6   system_IS_WINDOWScompiler  	frozensetrV   r+   rY   r[   r\   rU  r]   RLockr  r  r  hermes_cli.colorsr   r   hermes_cli.default_soulr   r   r   r   r   r  r   r   r   r   r   r   r  r  r  r  r!  r"  r'  r-  r2  rC  r  r   r  rE  rF  rI  rL  rN  tuplerZ  r`  rg  rp  rr  rw  r  r  rK  r  r%  r)  r,  r;  rC  rL  rW  rX  r_  r  r  r  r  r  r  r  r  r  r  r  r  r  r  _VALID_CUSTOM_PROVIDER_FIELDSr  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rW  r
  r  rJ  r  r  rB  r@  rC  rN  r  rj  rk  _COMMENTED_SECTIONSr   r}  r  r  r  r  r  r  r   r  rV  r  r  rX  r  r  r  r  r  r  r'  r  r	  r  r  rW  rd  rs  ru  r  r  r  r  r   rZ   r$   <module>r     s7A        				  				       



       ! ! ! ! ! !       8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9		8	$	$
  CEE c ! ! !3 3$ 3 3 3 3l*D *y *T * * * *X ho9,2:9:: P *3 4 4 4 * * 	#   *
C 
D 
 
 
 
  13 tCH~ 2 2 2 fh DeCc3S#XSRZ[^R_M_H`$`aab g g g AC 4U3T#s(^#;<<= B B B y   ) 6 6 6 6 6n  + + + + + + + + L L L L L L L L , 	  HSM    ,D , , , , {HSM    2 2x~ 2 2 2 2 2J J J# J J J JZt      HTN d    *D    8# #    $9C 9 9 9 90K 6"c " " " " 3 s    8; ;# ; ; ; ;*$ * * * *d - , , , , , 0 0 0 0 0 0 0 0- - - - -&d & & & &2$ 2 2 2 2x}hsm'C!D    <   :  @t    2  $$ 4    *!& !& !&H"d " " " "4W#RW#W# "W# !"	W#
 W# tW# W#  kRk 	4k* 	 +k< 	1=k> 	?kJ 	Kk^ 	"6_kj 	#Dkk| 	&t}kL 	TMkZ 	B[kx 	&ykF 	rGkP 	4QkV 	QWkj 	&kkr 	"3skJ   $' ,0 #Uk k kW#v  _7_f_ 	s_ 	3	_ 	$S_ 	2_* 	V+_D 	BE_^ 	d__` 	Da_b 	bc_n 	bo_p 	Rq_r 	Cs_t 	Eu_x 	y_z 	D{_|   $  */  $)
 !}_ _ _wW#x 
#	 yW#F	 ! # '+!&
 (
 $) "' &+#9
 
+& &G	W#j
  
 !   5 k
W#p dqW#z 7{W#X SYW#x  yW#J  "!"&'
 
 !"&'
 
 KW# W#h &* %* "& )- K5 5iW#^  	#D	_W#x TyW#^ !  _W#n ! $
 
 %'!#&-
 
 oW#F  r 	Qr 	 "
 
r" 	
 
#r2 	
 
3rJ 	
 
KrZ 	
 
[rj 	
 
krz 	
 
{rL 	
 
Mrf 	
 
gr@ 	
 
ArX 	
 
Yrr 	
 
srN 	
 
Ort 	
 
urD 	
 
ErT 	
 
UrGW#n  F5FrF 	&F 	BF 	 F 	%cF 	%aF  	 !F" 	;#F* 	!$+F6 	U7F@ 	!%AFJ 	DKFL 	EMFN 	%OFV 	%WFb 	cF Fd 	UeFf 	egFh 	"7iFp 	TqFr 	&ssFz 	4{F| 	}FL 	!$MFV 	4WFd 	$TeFf 	UgFh 		iFr 	DsFx 		yFH 	IFJ 	!
 !
KFR 	%dSF FT "'#% 
 !% #/  " !"& %d+#U+
 
 555
 
     "  %
 
eF F FoW#@  !& 
 
4 #$
 
(  "
 
0 Ec cAW#L! eM!W#^! 
'

 /0
 

 '
 
 4  
 $&
 
  	
 
 ->
 

 3	
 
 *	
Y9 9_!W#T# 
 !
 

 [
 *
 $ %	
 
# U#W#F$ !$  G$W#X$  Y$W#p$ ,q$W#z$  $  !
 - {$W#t%  !%  #!"
 #$  $ "'u; ;u%W#t' Ru'W# W# W#H( 
 	RI(W#^( 
# !/)DD!-8RSS% ,8B]^^" 

 _(W#T) 
   "  %  M' 'U)W#z*   !   
 
E& &{*W#P, bQ,W#X, Y,W#^, "$	 _,W#n, "$"'', "$ !  !&
 !) #   
 
U: :o,W#h.  i.W#x. " 
 
	 y.W#N/ "$	 O/W#^/ ! _/W#z/   # &*= {/W#~0 0W#B1 bC1W#b1 bc1W#t1 Ru1W# W# W#@2 A2W#H2 RI2W#N2 # 
 
   $5 O2W#H3   < !# 
 
  * !
 " y= =I3W#R5   $ &(  $3#$ !#  (,
  $% +0i5 5S5W#B7  		C7W#r7     %& "!
 
s7W#`8  a8W#v8 R   w8W#X9  	e	Y9W#j9  %' #A
"  
 
 u
 $-   &( # '* "2
Mo ok9W#Z=     
 &); [=W#d>    # ! !&; e>W#j?   k?W#@@  #   *1
 #K& &A@W#fA 
    # C" "gAW#xB  '   yBW# W#^C 	  !3!$
 "& ! ;
 
 f !")*%) 	"   8    mFW# W# W#@G YXX "67E E E		- - T#tCy.)     p6B p W&+"# p& U,7 'p6 L"7 7pF ;= GpV f
 oE   Wpn $& op~ .: pN T&* Op^ cA _pn N4 op~ 4@ pN [& Op^ =  _pn =  op~ 5A pN 0 . Op p^ :; _pn 6(. op~ 2-. pN <H Op^ )$' _pn 3< op~ *%) pN 4@ Op^ 8#( _pn 2> op~ 9+* pN :F Op^ D$7 _pl @% mpz W%> {pH `& IpX [B Yp p ph V() ipx 7C ypH	 U') I	pX	 6B Y	ph	 o&7 i	pv	 K9 w	pF
 W(, G
pV
 X= W
pf
  B'0 g
pt
 _= u
pD XZ EpT _ Upd I)& epr e* spF J . GpV N$%. Wpf F%'. gp p pv O= wpF jM GpV  o. Wpf  j+ gpv  O+   wpF L",. GpV U<3 Wpf Z3. gpv n')$o6 wpF \*)$o6 GpV n')$o6 Wpf Y1$o6 gpt z;=\\\ upF r&:$o6 GpV t#:$o6 Wph C "$45 ipx R 8"# yp p pH X:5'6 IpX f&'"$9: Yph P#, ipv \ 3 wpP N"6 Qp` M"= app V$3 qp@ V!F ApT G"'"# Upd V@ epr L%/$% spB e%	 CpR V''&' Spf M $ gpz ?$%#$ {pJ h%	 KpZ `%* [p p pn L&!" op| O'	 }pL !@'+# #MpZ !@'+# #[ph RL! !ip| ;&' }pJ l?) KpX z1	 Ypd @%< epr P> sp@  a6 ApN u / Z+	 	Opb U / L+	 	cpv  a, I+ wpF L)/ GpT F( Upb SA! !cp p pp !z0# #qp~ (c?* *pL P)6 MpZ L' [ph B1 ipv e= wpD s: EpT !\<# #Upd Y= ept W; upD g= EpT  ]' Upd h*) epr c/ sp@  c@" "ApN "F/$ $OpX G!	 Yp p pd C$	 epp K$ qpz V* {pD M& EpN U# OpX =(   Ypb T# cpl C mpz ; {pH B  IpV :' Wpf B% gpv f0   wpF   L2 G pV   V' W pf  A# g pv   M# w p pH! u)  yQ  [.  l0  K   u"  S!  `.% % i+' 'S#p p p l#  d38n9M    ,0C 0 0 0 0l    CH~  	
  
#s(^   64S#X#7    8,tDcN'; , , , ,j #&#%% C ' ' '"%,/8;	    r r rr r d38n	r r r rp $ $ $$ $ d38n	$ $ $ $Ns tDcN?S     (,/ /T#s(^$/	$sCx./ / / /dc htn      8<'+" ""tDcN34" T#s(^$" 
#s(^	" " " "P 8<'+	8 8S>88 tDcN348 T#s(^$	8
 
8 8 8 8O3 O4S> O O O O& 8<'+    tDcN34  T#s(^$  
#s(^	       L 8<'+	. .S>.. tDcN34. T#s(^$	.
 
. . . .4 8<'+	> >>> tDcN34> T#s(^$	>
 c]> > > >B# #    eCHo    J   ! ! !   WVV         M MhtCH~&> M$}J] M M M M`0 0(4S>": 0d 0 0 0 0,*4 *4$sCx.)A *4T *4 *4 *4 *4ZtCH~ $    .  D T#s(^    Dd d t    (D s uT3Y7G    .  (   2  = = = =@4S> c%S/6J    6  .48- -cN-38n- Cc3h01- 
#s(^	- - - -`QtCH~ Q$sCx. Q Q Q QhS#X 4S>    > GK + + +$sCx.) +# + +s + + + +^#c3h # # # #L htn PT    03T 3 3 3 3 3 3 341T#s(^ 1 1 1 1"2d38n 2 2 2 28    
 
 
   8~' 
> !	
 3 + 7 5 ) - # # # ! -  3!" /#$ </'%M/5A'Q;)39   @s s    : :# : : : : %)'+#	( ( (	$sCx.	!( T#s(^$( tn	(
 
#s(^( ( ( (Vj jc3h j j j jZ $ 2 F  48	c\ c\ c\cNc\ c\ Cc3h01	c\ c\ c\ c\L     .;$sCx. ; ; ; ;F [_
HU5huox}!DEtCQTH~UVW ^ ^ ^	 	 	 	 . - - - - - -Ot O O O O Od+3 + + + +\%S % % % % % %PC C     V VC V V V VrF# F$ F F F FR$ $c $ $ $ $$ $ $ $" "# " " " "s 3 4S>    C    ,s x}    #S #Xc] # # # #<BC BC B B B B  i ! ! !   ( s C     :L L L^%/ %/ %/Pb@# b@c b@ b@ b@ b@RF F F\ #    >     . &+ "C C C CN !   " " " " "rZ   