
    epjz                    .   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 ddlmZ  ej        e          Z e             Z!e e"d<   d	ed
ee         fdZ#ddd	e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e0e0e0e0ee%ef         ee%ee%         f         f         f         e"d<   i Z1ee%ee0e0ee%ef         f         f         e"d<    ej2                    Z3 e+h d          Z4ddl5Z5ddl6m7Z7m8Z8 ddl9m:Z:m;Z; dZ<dddZ= ed           Z> e+d!d"h          Z?d
ee%         fd#Z@d
eAfd$ZBd%ZCd
ee%         fd&ZDd(d'ee         d
efd(ZEd(d'ee         d
e%fd)ZFd
eAfd*ZGd(d+e%d'ee         d
dfd,ZHd+e%d
e%fd-ZId
e%fd.ZJd/ZKd
e%fd0ZLd)d2e%d
e%fd3ZMd*d2e%fd5ZNd
eeO         fd6ZPdd7lQmRZRmSZS dd8lTmUZUmVZV d
efd9ZWd
efd:ZXd
efd;ZYd
eZee0         ee0         f         fd<Z[d+d=Z\d> Z]d
eAfd?Z^d@ Z_dAed
dfdBZ` e             Zae e"dC<   dD ZbdAefdEZcddFldmeZemfZf g dGdHdIgg dJdKgdLgdMZgee0ee%         f         e"dN<   i Zhd,dPeAd
eee%ef                  fdQZidRe%fdSZjdTdTdOdUdVee%ef         dWeAdXeAdYeAd
ee%ef         f
dZZk el            ZmdRe%fd[ZndRe%d
eAfd\Zode%d
eAfd]Zpd^eAd
e%fd_Zqd
eee%ef                  fd`Zrd
eee%ef                  fdaZs e             Zte e"db<   dce%dde%dee%dfed
df
dgZudhdidjedce%d
eee%ef                  fdkZvdhdidjedce%d
eee%ef                  fdlZwdmed
eee%ef                  fdnZx	 d(doeee%ef                  d
eee%ef                  fdpZydqed
eeA         fdrZz	 	 d-dse%dteeee%ef                           doeee%ef                  d
ee%ef         fduZ{	 	 d-dvee%ef         dse%dteeee%ef                           doeee%ef                  d
df
dwZ|dxed
ee%e%f         fdyZ}	 	 d-dse%dteeee%ef                           doeee%ef                  d
ee%e%f         fdzZ~	 	 d-dvee%ef         dse%dteeee%ef                           doeee%ef                  d
df
d{Z	 	 d-d|e%dse%dteeee%ef                           doeee%ef                  d
ee0         f
d}Zdqed
e0fd~Zd
eAfdZd
ee0e0f         fdZh dZ e+ eej                              ez  Zh dZh dZe G d d                      Zd(doeee%ef                  d
ed         fdZd(doeee%ef                  d
dfdZd(doeee%ef                  d
dfdZdoee%ef         d
dfdZd.deAdeAd
ee%ef         fdZdeOdeOd
eOfdZdeOdeOd
eOfdZdeOde d
eeOe f         fdZdej        d
e%fdZde%d
ee%         fdZd Zd(dZd Zd(dZdoee%ef         d
eee%df                  fdZeedfdoee%ef         dee%ef         deeee%df                           d
ee%ef         fdZdoee%ef         d
ee%ef         fdZdoee%ef         d
ee%ef         fdZdeee%ef                  d
eAfdZdddeee%ef                  de%ded
efdZd
ee%ef         fdZd(d	ee         d
ee%ef         fdZd
ee%ef         fdZd(d	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ZdOdde%de%dqedeAd
df
dZi ddddLddddddddddddēddƓddȓddʓdd̓ddΓddГddғddԓdd֓ddddddddddddddZdqed
e%fdZde%d
ee%         fdZdddddeee%e%f                  doeee%ef                  deeA         d
ee%e%f         fdZdeAd
ee%ef         fdZdZdZdZdTddOddoee%ef         deAdeeee%df                           deAfdZde%d
e%fdZd
ee%e%f         fdZdaeeee%ee         ee0         f         ee%e%f         f                  e"d<   d+dZded
efdZd
e0fdZde%dqe%d
e%fdZdqe%d
e%fdZde%de%d
eAfdZde%dqe%fdZd e%d
e%fdZde%d
eAfdZd(dqe%fdZd(dZd(dqe%fdZde%dqe%d
ee%ef         fdZd
e0fdZde%d
ee%         fdZde%d
ee%         fd	Zde%d
e%fd
Z e+h d          ZƐd/dqede0d
efdZǐd ZȐd Zde%d
ee%         fdZ	 d(doeee%ef                  d
eAfdZ	 d(de%doeee%ef                  d
eAfdZd
eee%ef                  fdZ	 d(de%dqedoeee%ef                  d
dfdZdRe%fdZ e+h d          Z e+h d          Z e+dth          Z e+dh          Zd
e e%         fdZԐd0de%de e%         ded
ee%         fdZde%d
eZeAee%         f         fdZ֐d,de%dqe%d eAfd!ZdOd"de%d^eAfd#Zde%fd$Zِd% ZdOaېd+d&Z eܦ             dOaݐd+d'Z eަ             dS (1  a8  
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 get      - Print a resolved configuration value
- hermes config set      - Set a specific value
- hermes config unset    - Remove a user configuration value
- hermes config wizard   - Re-run setup wizard
    N)	dataclass)Path)DictAnyOptionalListTupleSet)normalize_route_base_url)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/thesage/.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defaultsfallbackexcr)   c                8   	 |                                  }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k    r
d|  d| d}n	d|  d| d}|	|d| d	z  }t          	                    |           	 t          j                            d
| d           t          j                                         dS # t          $ r Y dS w xY w)u  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``.

    ``fallback`` selects the message wording: ``"defaults"`` (fresh process,
    nothing else to serve) or ``"last-known-good"`` (in-process retention of
    the previously loaded config — see the codex#31188 port in
    ``_load_config_impl``).
    r   Nlast-known-goodzFailed to parse : u{   . Keeping the previously loaded config for this process — edits to config.yaml are being IGNORED until the YAML is fixed.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)   r    keyr"   msgs          r%   _warn_config_parse_failurer;   c   s   2';< ' ' ';A&'
"""S!!!(55K$$$O{ O Oc O O O 	){ ) )c ) ) ) 	 K[KKKK
NN3
:3:::;;;
   s"   03 AAAD 
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_DENYLISTr9   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)r[   
ValueError)r9   s    r%   _reject_denylisted_env_varr^      s@     $$$'C ' ' '
 
 	
 %$    _LAST_EXPANDED_CONFIG_BY_PATH_LOAD_CONFIG_CACHE_RAW_CONFIG_CACHE>l   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COPILOT_CLI_PATH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COPILOT_ACP_BASE_URLDISCORD_HOME_CHANNEL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HERMES_ACP_AUTH_METHODMATRIX_REQUIRE_MENTIONQQ_GROUP_ALLOWED_USERSWECOM_CALLBACK_CORP_IDWEIXIN_ALLOW_ALL_USERSHERMES_ACP_AUTO_APPROVEHERMES_COPILOT_ACP_ARGS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HERMES_COPILOT_ACP_COMMAND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NixOS)nixnixosz
/nix/storebrewhomebrewc                  0   t          j        dd                                          } | rE|                                 }|t          v rdS |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 Nr   z.managed)
osgetenvstriplower_IGNORED_MANAGED_VALUES_MANAGED_TRUE_VALUES_MANAGED_SYSTEM_NAMESgetget_hermes_homer   )raw
normalizedmanaged_markers      r%   get_managed_systemr   c  s    
)$b
)
)
/
/
1
1C
 :YY[[
0004---7$((S999$&&3N w4r_   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    r_   r%   
is_managedr   t  s     t++r_   zUpdate Hermes through the Nix source that installed it (e.g. nix profile upgrade, or update your flake input and rebuild with nixos-rebuild or home-manager switch)c                  <    t                      } | dk    rt          S dS )z;Return the preferred upgrade command for a managed install.r   N)r   _NIX_UPDATE_MSG)managed_systems    r%   get_managed_update_commandr     s#    '))N  4r_   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. 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     s.     >> '//111r_   c                    t          |           }h d}	 |dz                      d                                                                          }||v r|S n# t          $ r Y nw xY w	 t                      dz                      d                                                                          }||v r|dk    rt                      r|S n# t          $ r Y nw xY wt                      }|r(|                                                    dd          S 	 |	                                }|t          k    rt          |j        v rdS n# t          $ r Y nw xY w|d	z  }|                                rd
S |                                rQ	 |                    d                                          }|                    d          rd
S n# t          $ r Y nw xY wdS )u	  Detect how Hermes was installed: 'docker', 'nix', 'nixos', 'git', or 'unknown'.

    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 managed mode)
    4. /nix/store/ path detection -> 'nix' (nix run / nix profile install)
    5. .git directory presence -> 'git'
    6. Fallback -> 'unknown'

    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 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`` checks and behaves like any off-path install.
    See issue #34397.
    >   gitr   r   dockerunknown.install_methodutf-8encodingr    -r   z.gitr   zgitdir:r   )r   	read_textr   r   r   r   _running_in_containerr   replacer   
_NIX_STOREparentsis_diris_file
startswith)r   rootsupported_methodsmethodmanagedresolvedgit_pathcontents           r%   detect_install_methodr    s8   X (55DDDD**55w5GGMMOOUUWW&&&M '   
!22YY((UWWUWW	 	 &&&(0B0BK`KbKb0BM    !""G 1}}&&sC000<<>>z!!jH4D&D&D5    f}H u  	(('(::@@BBG!!),, u 	 	 	D	9sI   AA 
A&%A&*A"C 
CC-E 
EE=G 
GG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r  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v rt           S | dk    rdS dS )zAReturn the update command or guidance for a given install method.>   r   r   r   z,docker pull nousresearch/hermes-agent:latestzhermes update)r   )r  s    r%   %recommended_update_command_for_methodr    s*    !!!==?r_   c                  z    t                      } | r| S t          t                                }t          |          S )z<Return the best update command for the current installation.)r   r  get_project_rootr  )managed_cmdr  s     r%   recommended_update_commandr"  (  s=    ,..K "#3#5#566F0888r_   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   r_   r%   format_docker_update_messager%  [  s
     "!r_   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|  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 switchz): 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*  e  s    '))@-@N
)$b
)
)
/
/
1
1
7
7
9
9C   $88866cmV*f * *'* * *	
	C& 	C 	C> 	C 	C 	Cr_   modify configurationc                 V    t          t          |           t          j                   dS )z+Print user-friendly error for managed mode.fileN)printr*  r5   r6   )r'  s    r%   managed_errorr0  x  s&    	
 
(
(sz::::::r_   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)r6  r7  r8  r:  )r   environr   r  r  r   openr   r	  	partitionFileNotFoundError)r  container_mode_fileinfofliner9   _valuer6  r7  r8  r:  s               r%   get_container_exec_inforE    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   get_process_hermes_home)atomic_replacefast_safe_loadc                  $    t                      dz  S )zGet the main config file path.config.yamlr   r   r_   r%   get_config_pathrL    s    },,r_   c                  $    t                      dz  S )z&Get the .env file path (for API keys)..envrK  r   r_   r%   get_env_pathrO    s    v%%r_   c                  b    t          t                    j        j                                        S )z'Get the project installation directory.r   r   r_   r%   r   r     s     >> '//111r_   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)r5   platformr   r;  r   r   intr]   )uid_strgid_struidgids       r%   _resolve_hermes_uid_gidr\    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)r\  r   chownr   AttributeErrorNotImplementedError)pathrZ  r[  s      r%   _chown_to_hermes_uidrc    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   r;  r   r   rW  r]   chmodr   ra  rc  )rb  mode_strmodes      r%   _secure_dirrj    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/cgroupr3  r   r   Nr   lxckubepodsF)	r   r;  r   rb  r   r<  readr   IOError)rA  cgroup_contents     r%   _is_containerrs    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   rs  r   rb  r   r0   rg  r   ra  )rb  s    r%   _secure_fileru  6  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   ru  )rv  	soul_pathr$   s      r%   _ensure_default_soul_mdrz  H  s     y I 	 **G*<<HH+, 	 	 	FF	&x00 	F7;;;s   2 AA_HERMES_HOME_ENSUREDc                     t                      } t          |           }|t          v r|                                 rdS | j        j        dk    r'|                                 st          d|  d          t                      rRt          j
        d          }	 t          |            t          j
        |           n~# t          j
        |           w xY w|                     dd           t          |            dD ]-}| |z  }|                    dd           t          |           .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).

    Memoized per home path: this runs on EVERY ``load_config()`` (inside the
    config lock), and the ~14 mkdir/chmod syscalls per call made repeated
    config loads the dominant cost of hot read paths like ``model.options``.
    After the first successful pass for a given ``HERMES_HOME`` we only re-run
    the full walk if the home directory itself has vanished (a deleted home is
    recreated on the next load, as before). Profile switches change
    ``get_hermes_home()`` and therefore re-run for the new path.
    N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skills)r   r0   r{  r  r   r   r   r>  r   r   umask_ensure_hermes_home_managedr  rj  rz  r2   )rv  r9   	old_umasksubdirds        r%   ensure_hermes_homer  c  sa    D
d))C
"""t{{}}"
 {:%%dkkmm%=$ = = =
 
 	
 || &HUOO		 '---HYBHY

4$
///D
 	 	F vAGGD4G000NNNN%%%S!!!!!s   B; ;C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  rz  )rv  r  r  s      r%   r  r    s    ;;== 
54 5 5 5
 
 	
 ;  6Mxxzz 	 9 9 9  	 
F]Y%%dT%BBBD!!!!!r_   )DEFAULT_CONFIGOPTIONAL_ENV_VARS)FIRECRAWL_API_KEYBROWSERBASE_API_KEYBROWSERBASE_PROJECT_IDFAL_KEYVOICE_TOOLS_OPENAI_KEYELEVENLABS_API_KEY)r   ro   WHATSAPP_ALLOWED_USERSSLACK_BOT_TOKENSLACK_APP_TOKENSLACK_ALLOWED_USERSTAVILY_API_KEYTERMINAL_MODAL_MODE)         
      ENV_VARS_BY_VERSIONFrequired_onlyc                 .   g }t                                           D ]1\  }}t          |          s|                    d|i|ddi           2| sEt	          j                    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appendr  )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77 	Q 	QNHd ** QODO-OOPPPNr_   
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   rW  	TypeErrorr]   dictr   type__name__)	configr  rD  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T)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.
    api_keyNapiapi_modebase_url)r  r  pop)r  r  r  r  s       r%    clear_model_endpoint_credentialsr    s     i&&  #i&&&eT""" (j$''' (j$'''r_   c                 D   | }|                     d          D ]}t          |t                    r=	 |t          |                   }.# t          t
          t          f$ r t          cY c S w xY wt          |t                    r||vr	t          c S ||         }t          c S |S )z=Return a dotted-path value from nested dict/list config data.r.   )	r  r  r   rW  r  r]   
IndexError_MISSINGr  )r  r  r  r  s       r%   _get_nestedr  1  s    G  %%  gt$$ 
	 !#d)),z:6       && 	7""dmGGOOONs   AA)(A)c                    |                     d          }|sdS g }| }|dd         D ]}|                    ||f           t          |t                    r6	 |t	          |                   }E# t
          t          t          f$ r Y  dS w xY wt          |t                    r||vr dS ||         } dS |d         }d}t          |t                    rD	 |	                    t	          |                     d}nA# t
          t          t          f$ r Y dS w xY wt          |t                    r||vrdS ||= d}ndS t          |          D ]\  }}|i k    r nt          |t                    rk	 t	          |          }	n# t
          t          f$ r Y  n{w xY wd|	cxk    rt          |          k     r'n nY||	         i k    r|	                    |	           |}n4t          |t                    r|                    |          i k    r||= |} |S )z=Remove a dotted-path value from nested dict/list config data.r.   FNr^  Tr   )r  r  r  r   rW  r  r]   r  r  r  reversedlenr   )
r  r  r  r  r  r  r  removedr   r  s
             r%   _unset_nestedr  C  s   S!!E uGGcrc
  '''gt$$ 
	!#d)),z:6   uuu&& 	7""uudmGG559DG'4   	KKD		"""GG:z2 	 	 	55		GT	"	" w5DMu !))  b==Efd## 	$iiz*   C%%%%#f++%%%%%&+*;*;

3 %% 	&**T*:*:b*@*@tGNs6   A--B	B	$C9 9DD,E<<FFc                     d| v rdS |                                  }g d}||v p)|                    d          p|                    d          S )z;Return whether `hermes config set` routes this key to .env.r.   F)OPENROUTER_API_KEYrp   r   r  EXA_API_KEYPARALLEL_API_KEYr  FIRECRAWL_API_URLFIRECRAWL_GATEWAY_URLTOOL_GATEWAY_DOMAINTOOL_GATEWAY_SCHEMETOOL_GATEWAY_USER_TOKENr  r  r  BROWSER_USE_API_KEYr  TELEGRAM_BOT_TOKENDISCORD_BOT_TOKENTERMINAL_SSH_HOSTTERMINAL_SSH_USERr   SUDO_PASSWORDr  r  GITHUB_TOKENHONCHO_API_KEY)_API_KEY_TOKEN_SECRETTERMINAL_SSH)upperendswithr	  )r9   	key_upperapi_keyss      r%   _is_env_config_keyr    sj    
czzu		I
 
 
H 	X 	0?@@	0//r_   as_jsonc                   |rddl } |j        | d          S t          | t                    r| rdndS | dS t          | t          t
          f          r(t          j        | d                                          S t          |           S )	z.Format a config value for command-line output.r   NF)ensure_asciir   falsenull	sort_keys)
jsondumpsr  boolr  r   yaml	safe_dumprstripr0   )rD  r  r  s      r%   _format_config_get_valuer    s     5tz%e4444% ,+vvG+}v%$&& ?~eu555<<>>>u::r_   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   r'   r  prefixc                 `   |                                  D ]\  }}|                    d          r|s|n| d| }||vr                    ||d| d           Gt          |t                    r;t          |                    |          t                    r |||         |           d S )NrC  r.   zNew config option: )r9   defaultdescription)r  r	  r  r  r  r   )r'   r  r  r9   default_valuefull_key_checkr  s         r%   r  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===	> 	>r_   )r   )load_configr  r0   r  )r  r  r  s    @@r%   get_missing_config_fieldsr    sj     ]]FG> > > >c > > > > > > > F>6"""Nr_   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.   r9   )agent.skill_utilsr  r  r   logging	getLoggerr  debugr  r  r  r  r0   r   r  )r  r  all_varser  r  r  varstorage_keyr  r  rD  r  s                r%   get_missing_skill_config_varsr    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	signaturer:   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)r  r2   r3   r4   )r  r  r:   r  	dedup_keys        r%   _warn_once_per_providerr    sV     $i0I...""9---
N3r_   r   r  entryc                   t          | t                    sdS t          |           } 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t          |          |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>   r  urlr   modelmodelsr  r  r  r  provider	transport
extra_body
ssl_verifyr'  ssl_ca_certr  extra_headersr  discover_modelsr  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  r(  r  z	\{[^}]+\}uP   providers.%s: '%s' value '%s' is not a valid URL (no scheme or host) — skippedr   )r   r  r  r,  r)  r*  idc                 "    i | ]\  }}|d v	||S )>   r7  r   r   .0kvs      r%   
<dictcomp>z4_normalize_custom_provider_entry.<locals>.<dictcomp>  s/       A.1H1H11H1H1Hr_   r1  r-  r0  r/  r.  )r  r  r  r  setkeysjoinsortedurllib.parser6  r   r0   r   researchschemenetlocr3   r4   r   rW  floatr  normalize_extra_headers) r  r  _CAMEL_ALIASES_KNOWN_KEYScamelsnaker   r6  r  url_keyraw_url	candidateparsedr   raw_namer   r  r  r  
model_namer*  normalized_modelsitemmodel_id
model_metar  r  r1  r-  normalized_headersr/  r.  s                                    r%    _normalize_custom_provider_entryrX    s    eT"" t KKE '),	& 	&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  $F||
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
< r_   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  Nr  r  )r   r  r  r*  r  r  r1  r-  r0  r/  r.  r)  r  r  r,  )rX  r  r  )r  r  r   provider_entryfields        r%   )_custom_provider_entry_to_provider_configr\    s     2!%..9UE!  J t&+Z
-C%DN 6 6 J$.u$5N5!**4W*='Z&0&<{#r_   providers_dictc                     t          | t                    sg S g }|                                 D ]_\  }}t          |t                    rt          |          s*t	          |t          |                    }||                    |           `|S )zMNormalize ``providers`` config entries into the legacy custom-provider shape.r  )r  r  r  is_provider_enabledrX  r0   r  )r]  custom_providersr9   r  r   s        r%   "providers_dict_to_custom_providersra    s    nd++ 	-/$**,, 0 0
UeT"" 	+>u+E+E 	5e#c((SSS
!##J///r_   r  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.
    Nr  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 )Nr  r   r   r  /r)  )r0   r   r   r   r  r  r2   )	r  r  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)))))	* 	* 	* 	*r_   r`  	providers)r  r>  r   r   r0   r   r   r  r   rX  ra  )r  ri  r`  r  rf  rg  rh  s       @@@r%   get_compatible_custom_providersrk    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r_   rD  c                     | d S t          | t                    r| S t          | t                    r2|                                                                 }|dv rdS |dv rdS d S )N>   0nooffr  F>   r   onr   r   T)r  r  r0   r   r   )rD  lowereds     r%   _coerce_ssl_verifyrr  .  su    }t% % ++--%%''111500044r_   r  r`  c                 2   |#	 t          |          }n# t          $ r g }Y nw xY w| rt          |t                    si S t	          |           }|D ]}t          |t
                    st	          |                    d                    }|r||k    rCi }|                    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.  )
rk  r   r  r   r   r  r   r0   r   rr  )	r  r`  r  
target_urlr  	entry_urloutcaverifys	            r%    get_custom_provider_tls_settingsry  <  sE    	">vFF 	" 	" 	"!	" :&6== 	)(33J!  %&& 	,UYYz-B-BCC	 	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.r/  r.  N)ry  r   )r{  r  r`  r  tlss        r%   *apply_custom_provider_tls_to_client_kwargsr~  \  sa     +85Ev
N
NC
ww} :'*='9m$s&),&7l### r_   r0  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)r0   r9  s      r%   r=  z+normalize_extra_headers.<locals>.<dictcomp>x  s+    NNNtq!CFFCFFr_   )r  r  r  )r0  s    r%   rH  rH  j  sB     mT** - 	NN}':':'<'<NNNNr_   c                 |   |#	 t          |          }n# t          $ r g }Y nw xY w| rt          |t                    si S t	          |           }|D ]k}t          |t
                    st	          |                    d                    }|r||k    rCt          |                    d                    }|r|c S li S )a  Return ``extra_headers`` from a matching ``providers`` / ``custom_providers`` entry.

    Matches the entry whose normalized route identity equals *base_url*,
    mirroring :func:`get_custom_provider_tls_settings`, and returns its
    ``extra_headers`` dict, or ``{}`` when no entry matches or 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  r0  )rk  r   r  r   r   r  r   rH  )r  r`  r  rt  r  ru  headerss          r%   !get_custom_provider_extra_headersr  {  s     	">vFF 	" 	" 	"!	" :&6== 	)(33J!  %&& 	,UYYz-B-BCC	 	I33)%))O*D*DEE 	NNN	Irz  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  r  r   update)r{  r  r`  r  r0  mergeds         r%   4apply_custom_provider_extra_headers_to_client_kwargsr    sh     6h@PRXYYM -##$566<"==F
MM-   '-M#$$$r_   r)  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 t          |          }|sdS |D ]}t          |t                    st          |                    d                    }|r||k    rC|                    d          }t          |t                    sn|                    |           }	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 normalized route identity equals ``base_url`` 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  r   )
rk  r   r   r  r   r   r  rW  r  r]   )r)  r  r`  r  r   rt  r  ru  r*  r  raw_ctxctxs               r%   "get_custom_provider_context_lengthr    s   0   t	D>vFF 	D 	D 	D~tt**/00C&0d&;&;Css		D
 &-- t)(33J t!  %&& 	,UYYz-B-BCC	 	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   )r  r  rW  r  r]   max)rD  versions     r%   _coerce_config_versionr    s`    % qe**z"   qqw??s   ) >>c                     t                      } |                                 sdS 	 t          | d          5 }t          |          pi }ddd           n# 1 swxY w Y   n# t          $ r Y dS w xY wt          |t                    od|v S )uy  True when config.yaml exists, parses, and carries a ``_config_version`` key.

    Distinguishes an ANCIENT config (explicit old version → refused by the
    v12 support floor) from a fresh minimal/hand-written/cloned config with
    no version key at all (→ migrated + stamped normally). Missing or
    unparseable files return False so they never trip the floor gate.
    Fr   r   N_config_version)rL  r   r<  rH  r   r  r  )r   rA  r   s      r%    _raw_config_has_explicit_versionr    s     "##K u+000 	*A ##)rC	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	*   uuc4  =%6#%==s4   A! A	A! AA! AA! !
A/.A/c                     t          t          j        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   N)r  r  r   rL  r   r<  rH  r   r;   r  r  )latestr   rA  r  r  r  s         r%   check_config_versionr    sR    $N$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 A;/B ;A??B A?B 
B/B*$B/*B/>   signalplugins	image_gen	platforms	video_genmcp_serverssession_resetfallback_modelprofile_routesreset_triggersrequire_mentionalways_log_localr`  platform_toolsetsmultiplex_profilessmart_model_routingstt_echo_transcriptsknown_plugin_toolsetsknown_builtin_toolsetsgroup_sessions_per_userfilter_silence_narrationthread_sessions_per_userunauthorized_dm_behavior>   r   r)  r*  r  r  r  r  r-  r.  r/  r  r  >   r  r  r  r  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)r  
__module____qualname____doc__r0   __annotations__r   r_   r%   r  r  i  s1         ..MMMLLL
IIIIIr_   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: ...r4   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://...rC  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)r  r   r  r   r  r  r  r>  r?  _CUSTOM_PROVIDER_LIKE_FIELDSrA  r   	enumerater  r  r	  _KNOWN_ROOT_KEYS)
r  issuescpcp_keys
suspiciousir  fbr  r9   s
             r%   validate_config_structurer  r  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  r5   r6   r7   r@  )r  r  linescimarkers        r%   print_config_warningsr    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	           |rddlm	}  |            }|
                    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_CWDNterminalcwdr.   >   r   r  autor.   u     [33m⚠[0m MESSAGING_CWD=u&    found in .env — this is deprecated.u     [33m⚠[0m TERMINAL_CWD=r   )display_hermes_homeu/   [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   r;  r   r  r   r  r  r  r  r  insertr5   r6   r7   r@  )	r  messaging_cwdterminal_cwd_envterminal_cfg
config_cwdconfig_has_explicit_cwdr  r  	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888888''))	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 )uT  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, no ``merge_existing``);
    centralising the call makes the invariant impossible to regress one
    migration at a time. Callers must pass the full raw config returned by
    ``read_raw_config()`` after in-place mutations (including key removals);
    deep-merging the on-disk file back in would resurrect keys the migration
    just deleted. Partial-save preservation for unrelated top-level sections
    belongs on ``save_config(..., merge_existing=True)``, not here.
    N)save_config)r  s    r%   _persist_migrationr  L  s    . r_   interactivequietc           
        * g g g d}	 t                      }|r|st          d| d           n# t          $ r Y nw xY wt                      \  }}ddlm}m}m} t                      }	|	o||k     o||k     }
|
r] |            }|d         	                    |           t          j                            d| d           |st          d	|            n ||||           t                      }|                    d
          }t          |t                     r	 ddlm} n# t          $ r d}Y nw xY w|rd}|                                D ]|\  }}t          |t                     s |||          }|s*d|d<   d}|d         	                    d| d           |s*|D ]}t          d	|            t          d| d           }|r||d
<   t)          |           	 ddlm} ddlm}  |t                                          d          |          }|D ]1}|d         	                    |           |st          d	|            2n2# t          $ r%}t2                              d|           Y d}~nd}~ww xY w||k     r|s|
st          d| d|            t7          d          }|r7|s5t          d           |D ]#}t          d|d          d|d                     $| r%|r"t          d            |D ]}|                    d!          rt          d"|d!                     |                    d#          rt9          d$|d%          d          }n+t;          d$|d%          d                                          }|rPt?          |d         |           |d&         	                    |d                    t          d'|d                     n%|d         	                    d(|d          d)           t                       t7          d          }|rd* |D             ntA                      **fd+|D             }tA                      }tC          |d,z   |d,z             D ]0}|"                    tF                              |g                      1|r9| r6|s3d- tI          |          D             }|rt          d.tK          |           d/           |D ].\  } }!t          d0|  d1|!                    dd2                      /t                       	 t;          d3                                          &                                }"n# tN          tP          f$ r d4}"Y nw xY w|"d5v rVt                       |D ]C\  } }!|!                    d!          r?t          d$|!                    d|                       t          d"|!d!                     n&t          d$|!                    d|                       |!                    d#          r(t9          d$|!                    d%|            d6          }n9t;          d$|!                    d%|            d6                                          }|r=t?          | |           |d&         	                    |            t          d'|             t                       Ent          d7           tS                      }#|#r%|d8         *                    d9 |#D                        ||k     r$|
s"t                      }||d:<   t)          |           tW                      }$|$r*| r'|s$t          d.tK          |$           d;           |$D ]=}|                    d<d=          }%t          d0|d>          d1|d          d?|% d@           >t                       	 t;          dA                                          &                                }"n# tN          tP          f$ r d4}"Y nw xY w|"d5v rTt                       t                      }	 ddBl,m-}& n# t          $ r dC}&Y nw xY w|$D ]
}|                    dDd2          }'|'rdE|' d@nd2}(t;          d$|d%          |( d                                          }|s|'rt]          |'          }|r[|& dF|d>          })t_          ||)|           |d8         	                    |d>                    t          d'|d>          dG|            n<|d         	                    d(|d>          dH|                    d<dI           dJ           t                       t)          |           nt          d7           |S )Ka<  
    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'     ✓ Normalized .env line formatting (z line(s) changed)r   )SUPPORT_FLOOR_VERSIONrun_migrationssupport_floor_messager  u   ⚠ hermes config: r/   u     ⚠ r  )validate_mcp_server_entryNFenabledTz Disabled suspicious MCP server ''u     ⚠ Disabled MCP server 'z' pending review)validate_toolset)validate_platform_toolsetsr  z(platform_toolsets validation skipped: %szConfig version:     → r  u0   
⚠️  Missing required environment variables:u      • r   r-   r  z
Let's configure them now:
r(  z  Get your key at: passwordr  promptr  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>	  s    555Aai555r_   c                 R    g | ]#}|d          v|                     d          !|$S )r   advancedr   )r:  r<  required_namess     r%   
<listcomp>z"migrate_config.<locals>.<listcomp>	  sC       V9N**1553D3D* 	
***r_   r  c                 \    g | ])}t          |          s|t          v |t          |         f*S r   )r  r  )r:  r   s     r%   r  z"migrate_config.<locals>.<listcomp>	  sK     
 
 
 &&
 ,03D+D+D $T*++D+D+Dr_   
  z$ new optional key(s) in this update:u       • u    — r   z  Configure new keys? [y/N]: n>   yr   z (Enter to skip): z1  Set later with: hermes config set <key> <value>r  c              3   &   K   | ]}|d          V  dS )r9   Nr   )r:  r[  s     r%   	<genexpr>z!migrate_config.<locals>.<genexpr>;	  s&      &P&PuU|&P&P&P&P&P&Pr_   r  z! skill setting(s) not configured:skillr   r9   z (from skill: r  z#  Configure skill settings? [y/N]: )r  zskills.configr  z (default: r.    = u    — skill 'r  z' may ask for it later)0sanitize_env_filer/  r   r  hermes_cli.config_migrationsr  r  r  r  r  r5   r6   r7   read_raw_configr   r  r  hermes_cli.mcp_securityr  r  r  toolsetsr  hermes_cli.toolset_validationr  r3   r
  r  r   inputr   save_env_valuer>  ranger  r  rA  r  r   EOFErrorKeyboardInterruptr  extendr  r  r  r0   r  )+r  r  resultsfixescurrent_ver
latest_verr  r  r  _explicit_versionfloor_refusedr:   r  raw_mcp_servers_validate_mcp_server_entrymcp_touchedserver_namer  r  issuer  r  ts_warningsw_ts_val_errmissing_envr  rD  missing_optionalnew_var_namesvernew_and_unsetr   r@  answermissing_configmissing_skill_config
skill_namer  r  default_hintr  r  s+                                             @r%   migrate_configr$  f  sJ    CCG!## 	V 	VTETTTUUU    344K$          9:: 	%//	%*$ 
  4##%%
""3''' 	
6s666777 	".3..!!! 	{GU333 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m???:??@@@ '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Nsm   %/ 
<<D" "D10D1 A+I 
I;I66I;"3U U,+U,>3_2 2``-`4 4aar   overridec                 F   t          j        |          }|                                 D ]w\  }}||vrt          j        |          ||<   !t          |                    |          t
                    r.t          |t
                    rt          |||                   ||<   x|S )u  Merge *override* over *raw* for partial ``save_config`` writes.

    Top-level sections omitted from *override* are preserved from *raw*.
    Shared top-level dict sections are deep-merged so a caller can update one
    nested key without dropping sibling keys from disk. Intentional key
    removals within a section are not supported here — migration writes must
    route through ``_persist_migration`` with a full ``read_raw_config()`` dict
    instead.
    )copydeepcopyr  r  r   r  _deep_merge)r   r%  resultr9   rD  s        r%   _merge_partial_saver+  p	  s     ]8$$Fiikk : :
Uf-..F3KK

3.. 	::eT3J3J 	:%eVC[99F3KMr_   basec                 L   |                                  }|                                D ]z\  }}||v rJt          ||         t                    r/t          |t                    rt	          ||         |          ||<   S||v rt          ||         t                    r|u|||<   {|S )ut  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.

    An empty section key in config.yaml (``terminal:`` with no value) parses
    as YAML ``None``; treating that as an override would replace the entire
    default dict with ``None`` and crash every downstream consumer that
    expects a mapping (#58277). A ``None`` override of a dict default is
    ignored — same as the key being absent.
    )r'  r  r  r  r)  )r,  r%  r*  r9   rD  s        r%   r)  r)  	  s     YY[[Fnn&& 
  
 
U6MM6#;-- 5$''  &fSk599F3KKF]]z&+t<<]F3KKMr_   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  r  r  r2   )r.  r/  strippeddottedr  nodeps          r%   _strip_dotted_keysr5  	  s     EEH 
! 
!S!!ss 	 	AdD)) Qd]]7DDdD!! 	!eBi4&7&7U2YLL   =r_   mc                 b   |                      d          }|                      d                                          }|                    d          rn|t          d          d                                         }|s|S t          j                            |          }||S t                              d||           |S d|v rMt          j
        d|          r8t                              d||                    dd          d                    |S t          j                            ||          S )	u4  Expand one ``${...}`` config reference.

    Two accepted shapes, matching what MCP server config already resolves
    (``tools/mcp_tool.py::_env_ref_name``):

    * ``${VAR}`` — legacy bare name, resolved via ``os.environ``.
    * ``${env:VAR}`` — Cursor-style SecretRef, same resolution after the
      ``env:`` prefix is stripped.  Before this, the prefixed form worked in
      MCP config but stayed a literal string in config.yaml — a confusing
      half-support.

    Other SecretRef sources (``file:``, ``bitwarden:``, ``vault:``, ...)
    are NOT resolved here — external secret backends inject their values
    into the environment at startup (the ``secrets:`` block), so a config
    ref only ever needs the env shape.  Unknown prefixes warn once and stay
    verbatim so callers can detect them.
    r   r  env:NzTConfig ref %r: %s is not set (check ~/.hermes/.env); keeping the literal placeholder:^[a-z][a-z0-9_-]*:u   Config ref %r uses source %r which is not resolvable in config.yaml — external secret sources inject env vars at startup, so reference the variable as ${env:NAME} instead)groupr   r	  r  r   r;  r   r3   r4   rC  matchr  )r6  r   innerr   vals        r%   _env_expand_matchr?  	  s$   $ ''!**CGGAJJE S[[\\"((** 	JjnnT""?J./2D	
 	
 	
 

e||!6>>|
 	H S!$$Q'		
 	
 	
 
:>>%%%%r_   refc                     |                                  } |                     d          r-| t          d          d                                          }|pdS d| v rt          j        d|           rdS | S )zNormalize a ``${...}`` body to the env-var name it reads, or None
    when the ref uses a non-env source and never touches the environment.r8  Nr9  r:  )r   r	  r  rC  r<  )r@  r   s     r%   _env_ref_var_namerB  	  ss     ))++C
~~f 3v;;<< &&((|t
czzbh4c::ztJr_   c                    t          | t                    rt          j        dt          |           S t          | t
                    rd |                                 D             S t          | t                    rd | D             S | S )a$  Recursively expand ``${VAR}`` / ``${env: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                 4    i | ]\  }}|t          |          S r   _expand_env_varsr9  s      r%   r=  z$_expand_env_vars.<locals>.<dictcomp>	  s'    ???41a#A&&???r_   c                 ,    g | ]}t          |          S r   rF  )r:  rT  s     r%   r  z$_expand_env_vars.<locals>.<listcomp>	  s!    7774 &&777r_   )r  r0   rC  subr?  r  r  r   )objs    r%   rG  rG  	  s     #s >vn&7===#t @??399;;????#t 87737777Jr_   c                    |i }t          | t                    rLt          j        d|           D ]5}t	          |          }|"t
          j                            |          ||<   6ngt          | t                    r(| 	                                D ]}t          ||           n*t          | t                    r| D ]}t          ||           |S )u  Map every ``${VAR}`` / ``${env: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).

    ``${env:VAR}`` refs are tracked under the real variable name; refs
    with a non-env source prefix never read the environment, so they are
    excluded from the snapshot.
    NrD  )r  r0   rC  findallrB  r   r;  r   r  values_env_ref_snapshotr   )rJ  snapshotr   r   rD  rT  s         r%   rN  rN  
  s     #s 
.:nc22 	6 	6C$S))D!#!5!5	6 
C		 .ZZ\\ 	/ 	/EeX....	/	C		 . 	. 	.DdH----Or_   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   )r  r   r  r   r0   )r  indexedrT  r   s       r%   _items_by_unique_namerR  "
  s    eT"" tG  $%% 	Z8H8H#-N-N 	44F|7??44Nr_   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   r  r  )r:  r9   rD  loaded_expandedr   s      r%   r=  z/_preserve_env_ref_templates.<locals>.<dictcomp>I
  sr     
 
 
 U ,,6,M,MW##C(((SW 
 
 
r_   Nc                     g | ]c}t          |                    |                    d                     (                    |                    d                     nd          dS )r   N)rU  r   )r:  rT  loaded_by_nameraw_by_names     r%   r  z/_preserve_env_ref_templates.<locals>.<listcomp>[
  s{         ,OODHHV$4$455<J<VN&&txx'7'7888\`   r_   c           
          g | ]a\  }}t          ||t                    k     r|         nd t          t                    r|t                    k     r|         nd           bS r  )rU  r  r  r   )r:  indexrT  rV  r   s      r%   r  z/_preserve_env_ref_templates.<locals>.<listcomp>c
  s     	
 	
 	
 t (#c#hh..E

Dot449>_AUAU9U9U  && 	
 	
 	
r_   )
r  r0   rC  rD  rG  r  r  r   rR  r  )r  r   rV  current_by_namerX  rY  s    `` @@r%   rU  rU  1
  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	
 	
 	
 		
 Nr_   .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.
    rD  rb  .r   Nc                     t          | t                    r,|                                 D ]\  }} |||fz              d S |r                    |           d S d S r  )r  r  r  r2   )rD  rb  r9   child_walkpathss       r%   r`  z%_explicit_config_paths.<locals>._walk~
  su    eT"" 	#kkmm , ,
UeTSF]++++F 	IIdOOOOO	 	r_   r   )r>  r   r	   r0   )r  r`  ra  s    @@r%   _explicit_config_pathsrb  q
  sh     #&%%ES c3h D        
E&"Lr_   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   rD  r  rb  .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(  r  r  r  r   )rD  r  rb  default_dictr1  r9   r_  child_defaultstripped_child_striprc  s            r%   rj  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###r_   )r>  r   r	   r0   r  r   )r  r'   rc  r*  r9   rD  r1  rj  s     `    @r%   _strip_default_valuesrk  
  s      **S1D"-E-EEM$c $C $uS#X $3 $ $ $ $ $ $ $,  Fllnn # #
U6%c!2!2SF;;"F3KMr_   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  r  )r:  r;  r  s     r%   r  z-_normalize_root_model_keys.<locals>.<genexpr>
  s>        

1     r_   )r+  r  r  rm  r  )r+  r  r  Nr  )r   r  r  anyr  )
r  model_inmodel_has_aliasmodel_needs_canonhas_rootr)  r9   root_val	alias_valaliass
   `         r%   _normalize_root_model_keysrw  
  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   		&$Mr_   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.
    agent	max_turnsN)r  r   r  r  )r  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!!!Mr_   provider_cfgc                 "   t          | t                    sdS |                     dd          }t          |t                    r|S t          |t                    r(|                                                                dvS t          |          S )u8  Return whether a ``providers.<name>`` config block is enabled.

    A provider is enabled by default. Only an explicit ``enabled: false`` in
    the block hides it from the model picker, ``/models`` listings, the
    runtime resolver and the doctor / status output.

    Backward-compat: configs without the ``enabled`` key keep working as
    before — the default is ``True``.

    Pass any non-dict (None, list, string) and you get ``True`` too, so
    malformed entries don't disappear silently; they'll still be flagged
    by the existing validation paths.
    Tr  >   rm  rn  ro  r  )r  r  r   r  r0   r   r   )r  flags     r%   r_  r_  -  s     lD)) tIt,,D$ $ Gzz||!!##+FFF::r_   )r  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
    )r  r  )r.  r  r?  r3  r9   s        r%   cfg_getr  F  sm    D c4   D  $%% 	NNNd??NNNCyKr_   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()``.
    N   r   r   r   r  )_CONFIG_LOCKrL  r   r1   r   r>  r   r0   rb   r   r'  r(  r<  rH  r   r;   r  r  )r   r    	cache_keypath_keycachedrA  datar  s           r%   r  r  u  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                      } 	 t          | d          5 }t          |          pi }ddd           n# 1 swxY w Y   n# t          $ r i cY S w xY wt	          |t
                    r|ni S )uo  Read a user ``config.yaml`` EXACTLY as written on disk.

    No DEFAULT_CONFIG merge, no managed-scope overlay, no ``${ENV_VAR}``
    expansion, no migration, no root-model normalization, no caching.

    ONLY legal for write-back round-trips and raw-file diagnostics —
    behavioral reads must use load_config()/load_config_readonly().

    Legal call sites, exhaustively:

      * WRITE-BACK ROUND-TRIPS (read → mutate one key → save): merging
        defaults or the managed overlay here would persist hundreds of
        default keys (or administrator-pinned values) into the user's file
        on the next save. Raw is *correct*, not an optimization.
      * RAW-FILE DIAGNOSTICS (doctor, deprecation sweeps): these inspect
        what the user actually wrote — stale root keys, drift against .env —
        and merged defaults would produce false positives.
      * PRESENCE-SENSITIVE ENV BRIDGES (gateway/send bridges that only
        export a key when the user explicitly set it): a defaults merge
        would make every key "present" and bridge the entire DEFAULT_CONFIG
        into the environment. These sites must still apply
        ``managed_scope.apply_managed_overlay`` + ``_expand_env_vars``
        inline, which they do.

    Semantics (deliberately mirrors the bare ``open()+yaml.safe_load()``
    pattern this replaces, so migrated sites keep their exact failure
    behavior):

      * missing file → ``{}``
      * unparseable YAML / other I/O errors → raises (callers that want
        fail-open already wrap in try/except; callers with last-known-good
        or warn semantics rely on the exception)
      * non-dict YAML root → ``{}``

    ``config_path`` defaults to :func:`get_config_path` (profile-aware).
    Pass an explicit path when the caller resolves its own home (gateway
    ``_hermes_home``, tui profile override, multi-profile probes).
    Nr   r   )rL  r<  rH  r>  r  r  )r   rA  r  s      r%   read_user_config_rawr    s    N %''+000 	+A!!$$*D	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+   			dD))144r1s3   A AA AA A	A AA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          	                    |          }|"|dd         |k    r|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 }t!          j        |          }|d         |d         |ft          |<   |cddd           S # 1 swxY w Y   dS )u  Fast-path variant of ``read_raw_config()`` for callers that ONLY READ.

    Returns the cached raw-config dict directly, skipping the per-call
    ``copy.deepcopy`` that ``read_raw_config()`` performs (needed there
    because some callers mutate the result before ``save_config``).

    **Mutating the returned dict corrupts the in-process cache for every
    subsequent caller.** Only use on read-only paths — e.g. per-turn policy
    checks like the shared-metrics gate, which runs 2-3x per agent turn and
    was paying a full config deepcopy each time.

    Same (mtime_ns, size) freshness key as ``read_raw_config()`` — an edited
    config.yaml is picked up on the next call.
    Nr  r   r   r   r  )r  rL  r   r1   r   r>  r   r0   rb   r   r<  rH  r   r;   r  r  r'  r(  )	r   r    r  r  r  rA  r  r  cached_copys	            r%   read_raw_config_readonlyr    s    
  	)++K!!##B4II!7+ 	 	 	II       
	 {##"&&x00&!*	"9"9!9       	kG444 /%a((.B/ / / / / / / / / / / / / / / 	 	 	&{A666IIIII%        	 $%% 	D mD))'0|Yq\;&O(#7                 s   E00;E0AE0AAE0.C)?CC)C!	!C)$C!	%C)(E0)
D3DDE0DAE00E47E4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 ()rL  r   r>  r   r  r<  rp  )r   r*   rA  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2r_   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   r_   r%   r  r  +  s     40000r_   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   r_   r%   load_config_readonlyr  <  s    ( 51111r_   )r   platform_key	field_keyc                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.
    r  N)r  r  
setdefaultr  r  r  )r  r  rD  r   r  r  platform_configs          r%   write_platform_config_fieldr  S  s     #&8_;==F!!+r22Ii&& (	'{**<<<Oot,, 2"1	,!&OIr_   r6  rj   
modal_moder  r  timeoutTERMINAL_TIMEOUTlifetime_secondsTERMINAL_LIFETIME_SECONDSdocker_imageTERMINAL_DOCKER_IMAGEdocker_forward_envTERMINAL_DOCKER_FORWARD_ENVsingularity_imageTERMINAL_SINGULARITY_IMAGEmodal_imageTERMINAL_MODAL_IMAGEdaytona_imageTERMINAL_DAYTONA_IMAGEvercel_runtimeTERMINAL_VERCEL_RUNTIMEssh_hostr  ssh_userr  ssh_portr   ssh_keyr   container_cpuTERMINAL_CONTAINER_CPUcontainer_memoryTERMINAL_CONTAINER_MEMORY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_SHM_SIZE TERMINAL_DOCKER_RUN_AS_HOST_USER(TERMINAL_DOCKER_PERSIST_ACROSS_PROCESSESTERMINAL_DOCKER_ORPHAN_REAPERTERMINAL_SANDBOX_DIRTERMINAL_PERSISTENT_SHELL)container_diskcontainer_persistentdocker_volumes
docker_envdocker_mount_cwd_to_workspacedocker_networkdocker_extra_argsdocker_shm_sizedocker_run_as_host_userdocker_persist_across_processesdocker_orphan_reapersandbox_dirpersistent_shellc                     t          | t          t          f          rt          j        |           S t          |           S r  )r  r   r  r  r  r0   )rD  s    r%   _terminal_env_valuer    s4    %$&& !z%   u::r_   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  )r9   r  s     r%   terminal_config_env_var_for_keyr    sC    F>>&!! t"&&s3v;;<<'8999r_   )envr  r%  r  c                    | t           j        n| }t                      }|                    d          }t	          |t
                    }|si }||n|}||nt                      }t	          |t
                    r|                    di           ni }	t	          |	t
                    s|S ||	                                n|                                }
t          	                                D ]\  }}||	vr
|	|         }|dk    r\t          |pd                                          }|dv r@t	          |t                    rt           j                            |          }|r||
v s||vrt          |          ||<   |S )ag  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.

    Explicit keys in the user config's ``terminal`` section are authoritative
    and override their matching env values.  Merged defaults only backfill
    missing env vars; they never replace unrelated exported/.env values.
    Nr  r  r   >   r  r  r.   )r   r;  r  r   r  r  r  r?  r  r  r0   r   rb  
expanduserr  )r  r  r%  target
raw_configraw_terminal_cfgfile_has_terminal_configshould_overrider.  r  explicit_keyscfg_keyenv_varrD  raw_cwds                  r%   apply_terminal_config_to_envr    s   " ;RZZCF ""J!~~j11)*:DAA# 2:2B..O&&&,@,B,BC.8d.C.CK377:r***LlD))  ,2+=L%%'''CSCXCXCZCZM399;; 9 9,&&W%e%+2&&,,..G...%%% 2**511 	9= 8 8WF=R=R1%88F7OMr_   r  c           
         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}t4                              |          }t7          |||dnd           |ddlm}  |t<          t          t>          f         tA          t#          j        |                              }|
*i }|
d         |
d         |
d         |
d         ||ft          |<   | rt#          j        |          n|cY d }~cd d d            S Y d }~nd }~ww xY wtC          tE          |                    }tA          |          }|#                                }|rtA          |          }t1          ||          }t#          j        |          t4          |<   |
Wt#          j        |          }tI          |          }|rtI          ||           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_scoperJ  )r   r   r  r  r  c              3   b   K   | ]*\  }}t           j                            |          |k    V  +d S r  )r   r;  r   r9  s      r%   r  z$_load_config_impl.<locals>.<genexpr>  s9      KKda2:>>!$$)KKKKKKr_   r   r   rz  ry  r,   r'   r(   castr  r  )%r  r  rL  r0   r   r1   r   r>  
hermes_clir  get_managed_dirr   ra   r   r  allr  r'  r(  r  r<  rH  r  r  r)  r   r`   r;   typingr  r   r   rG  rw  r~  load_managed_configrN  )r  r   r  r    user_sigr  managed_dirmanaged_cfg_pathmstmanaged_sig	cache_sigr  env_snapshotr  rA  user_configagent_user_configr  lkg_castlkg_copy
_empty_envr   expandedmanaged_configmanaged_expandedr  s                              r%   r  r    s>   	 O O%''{##	!!##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_O O O O O O O Ob ~..3R+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 'R 'R 'R 477AA*25/..z   
 ?
 544444/4uS#X(8s9K9K(L(L0 0H !,
 @B
%aL)A,%aL)A,$j8*84
 7DQ4=222QQQQQOO O O O O O O Of #????''RR 00KF0S0STT
#J// '::<< 	?/??"8-=>>H26-2I2I%h/  -11K,Z88L @!.,???+RY+R+R\+R+Rx( ! #"OO O O O O O O OL# ""8T222 _O O O O O O O O O O O O O O O O O Os   ,Q"AQA'$Q&A''&Q*B98Q9CQCCQ+QI6G7+I67G;	;I6>G;	?A6I65Q6
M B=M=M>QQMC
Q0QQ!Q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_defaultsrc  merge_existingr  r  c                Z   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}
|r|	rt-          |	|           } t/          t1          |                     }|}|	rt/          t1          |	                    ni }|r6t3          ||t4                              t9          |                              }d
h}|
r|                    |
           |r|                    |           |rB|r@ddlm}  |t@          t8          tB          f         |          }tE          |tF          |          }g }|                    di           }|r|                    d          |$                    tJ                     |                    di           }d}tM          |tN                    rtQ          d |D                       }nLtM          |tR                    r7tU          |                    d          o|                    d                    }|s|$                    tV                      ||||rd                    |          nd           tY          |           tZ          .                    t9          |          d           t          j        |          t4          t9          |          <   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.

    When ``merge_existing`` is True, the on-disk raw config is deep-merged
    under *config* before writing so partial callers (migration steps via
    ``_persist_migration``) cannot drop unrelated sections the caller omitted.
    Full-document replacement callers (dashboard raw YAML editor, callers that
    already deep-merge) must leave this False so intentional deletions survive.
    zsave configurationNr   r  zNote: zD managed setting(s) were not saved (managed by your administrator): r5  r-  r  re  r  )rc  securityredact_secretsr  Fc              3      K   | ]C}t          |t                    o)|                    d           o|                    d          V  DdS )r+  r)  N)r  r  r   )r:  r  s     r%   r  zsave_config.<locals>.<genexpr>  sN      gg_`jD11ZaeeJ6G6GZAEERYNNggggggr_   r+  r)  r   )extra_content)/r  r   r0  r  r  managed_config_keysr5  r'  r(  r/  r  r@  rA  r5   r6   r  r  r  rL  r  r  rb  r+  rw  r~  rU  r`   r   r0   r  r  r  r   r   rk  r  r  _SECURITY_COMMENTr  r   ro  r  r  _FALLBACK_COMMENTru  rb   r  )r  r  rc  r  r  managed_keys	_strippedr  r   _raw_for_pathsexplicit_raw_pathscurrent_normalizedr   raw_existingeffective_preserve_keysr   r  secr  fb_is_valids                       r%   r  r    s   * 
 \\ \\<< 	.///\\ \\ \\ \\ \\ \\ \\ \\ 	-,,,,,$88:: 	 24=3H3H, W WFI WS^^ W W8<		&BSBS8T8TW W   
 	,+++++%'',[999
 )**6DN">222$ 	  	An 	A(@@F 88STZ8[8[\\'
 &'B>'R'RSSS 	
  	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	
 	
 	
 	

 	[!!!c+..555:>-HZ:[:[%c+&6&67y\\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\ \\s   N MN  N$'N$	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  \>   r  r  r   r  )r   r  r  r@  )r  rD  quotedrP  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{Lr_   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 )	a  Load environment variables from ~/.hermes/.env.

    Normalizes line endings before parsing while treating each assignment's
    value as opaque data for boundary discovery.

    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), 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   errorsr5  r4  export r~  )rO  r   st_mtimer   r0   r>  r   
_env_cacher  r   r<  	readlines_sanitize_env_linesr   r	  r=  r   )env_pathmtimesizer  
cached_keycached_varsenv_varsopen_kwrA  	raw_linesr  rB  r9   rC  rD  s                  r%   load_envr2  <  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   r_   r%   invalidate_env_cacher4    s     JJJr_   r  c                     g }| D ]s}|                     d          }|                                }|r|                    d          r|                    |dz              [|                    |dz              t|S )a1  Normalize .env line endings without changing assignment semantics.

    Content after the first ``=`` is opaque value data. A known variable name
    embedded in that value must never be reinterpreted as another assignment;
    concatenated assignments are ambiguous and therefore remain on one line.
    z
r5  r/   )r  r   r	  r  )r  	sanitizedrB  r   r1  s        r%   r)  r)    s     I 	* 	*kk&!!99;;  	8..s33 	S4Z(((D))))r_   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 whose safe formatting was normalized. 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Kr_   .tmp.env_dirsuffixr  r  )rO  r   r<  r(  r)  absr  sumziptempfilemkstempr0   r   r   fdopen
writelinesr8   fsyncfilenorG  BaseExceptionunlinkr   ru  r4  )	r*  read_kwwrite_kwrA  original_linesr6  r  fdtmp_paths	            r%   r   r     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:  rB  s     r%   r  z._check_non_ascii_credential.<locals>.<genexpr>  s(      ::DKKK::::::r_   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  r5   r6   )r9   rD  	bad_charsr  r  r6  s         r%   _check_non_ascii_credentialr[    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 p8d| v p4d| v p0| |                                  k    pt          d | D                       }|s| S |                     dd                              dd          }d| dS )	zDQuote .env values containing characters with special dotenv meaning.r   r5  r  r  c              3   >   K   | ]}|                                 V  d S r  )isspace)r:  cs     r%   r  z#_quote_env_value.<locals>.<genexpr>  s*      **qqyy{{******r_   r  z\\z\")r   ro  r  )rD  needs_quotingescapeds      r%   _quote_env_valuerb    s    {{ 	u 	+%<	+%<	+ EKKMM!	+ **E*****   mmD&))11#u==Gw>>>r_   rB  c                     |                                  }|                    d          r|dd                                         }|                    | d          S )ux  True when a .env line assigns ``key`` — plain or ``export``-prefixed.

    ``load_env()`` accepts the bash-compatible ``export KEY=value`` form
    (#6659), so the writers must recognise the same shape. Otherwise a
    hand-added ``export`` line is invisible to save (duplicate appended) and
    remove (line survives → the value resurrects on the next load, #40041).
    r%  r~  Nr4  )r   r	  lstrip)rB  r9   r1  s      r%   _env_line_defines_keyre    sZ     zz||H9%% )ABB<&&((#yyy)))r_   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          |            |                    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 ]$\  }}t/          ||           r|  d|
 d||<   d} n%|sH|r+|d                             d          s|dxx         dz  cc<   |                    |  d|
 d           t5          j        t9          |j                  dd          \  }}d}|                                r=	 t=          j        |                                j                   }n# tB          $ r Y nw xY w	 tE          j#        |dfi |5 }	|	$                    |           |	%                                 tE          j&        |	'                                           ddd           n# 1 swxY w Y   tQ          ||           |'	 tE          j)        ||           n# tB          $ r Y nw xY wtU          |           n5# tV          $ r( 	 tE          j,        |           n# tB          $ r Y nw xY w w xY w|tD          j-        | <   t]                       dS )z)Save or update a value in ~/.hermes/.env.zset Nr   r  rN  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   Fr4  Tr^  r;  r<  r=  r  )/r   r0  r  r  is_env_managedr  r/  r5   r6   _ENV_VAR_NAME_REr<  r]   r^   r  r[  r  rO  r   r<  r(  r)  rb  r  re  r  r  rC  rD  r0   r   r   S_IMODEst_moder   r   rE  rF  r8   rG  rH  rG  rg  ru  rI  rJ  r;  r4  )r9   rD  r  r  srcr*  rK  rL  r  rA  serialized_valuefoundr  rB  rN  rO  original_modes                    r%   r  r    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 s++ 	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!	+I5 5
JJM	 AK5)M	 5K99M	 <K9=M	 L) (M	 )
L63M	 5L66M	 	
M;M)(M;)
M63M;5M66M;identityc                     t          j        ddt          | pd                                                                        d          }|rd| dndS )u  Env var name holding a custom endpoint's API key.

    ``identity`` is whatever names the endpoint on the calling path — the
    Desktop panel's endpoint id, or ``host:port`` for the CLI setup flow.
    Two properties matter:

    - It keys off the endpoint's own identity, not just its hostname, so two
      endpoints on one host (``127.0.0.1:8000`` and ``:8001``) get separate
      slots instead of the second save clobbering the first's credential.
    - The fixed ``HERMES_CUSTOM_`` prefix keeps the result a valid POSIX name
      even when the slug starts with a digit, which every IP-based local
      endpoint does (``127.0.0.1`` → ``127_0_0_1``). ``save_env_value``
      rejects digit-leading names outright.
    z
[^A-Z0-9]+rC  r   HERMES_CUSTOM_r  HERMES_CUSTOM_API_KEY)rC  rI  r0   r  r   )rt  slugs     r%   custom_endpoint_key_envry  w  sZ     6-c(.b&9&9&?&?&A&ABBHHMMD.2O*D****8OOr_   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   r  rN  rg  zCannot remove rh  ri  r-  rj  Nr"  r  r#  r   r   c                 4    g | ]}t          |          |S r   )re  )r:  rB  r9   s     r%   r  z$remove_env_value.<locals>.<listcomp>  s)    PPP$/DT3/O/OPPPPr_   r;  r<  r=  r  )(r   r0  r  r  rl  r  r/  r5   r6   rm  r<  r]   rO  r   r   r;  r  r<  r(  r)  r  rC  rD  r0   r   r   rn  ro  r   rE  rF  r8   rG  rH  rG  rg  ru  rI  rJ  r4  )r9   r  r  rp  r*  rK  rL  rA  r  	new_linesrr  rN  rO  rs  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PPPP%PPPI	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.rs   r   r   Nr  rD  save_fnwriters      r%   save_anthropic_oauth_tokenr    s8    &F
Fe$$$
F#####r_   c                 H    | pt           } |dd            |dd           dS )zHUse Claude Code's own credential files instead of persisting env tokens.rs   r   r   Nr  )r  r  s     r%   %use_anthropic_claude_code_credentialsr    s8    &F
Fb!!!
F#####r_   c                 H    |pt           } |d|             |dd           dS )zBPersist an Anthropic API key and clear the OAuth/setup-token slot.r   rs   r   Nr  r  s      r%   save_anthropic_api_keyr    s8    &F
F&&&
Fb!!!!!r_   c                 2    ddl m}  || |           d| ddS )Nr   save_provider_env_credentialTF)success	stored_as	validated)hermes_cli.credential_lifecycler  )r9   rD  r  s      r%   save_env_value_securer    sB     MLLLLL  e,,,  r_   c                  j   t                      } t          t          j                              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  )	r2  r>  r  r?  _EXTRA_ENV_KEYSr  r   r;  r   )r/  
known_keyscountr9   rD  s        r%   
reload_envr    s     zzH&+--..@JEnn&&  
U:>>#%''#BJsOQJE  h3"*#4#4
3QJELr_   c                    	 ddl m}m} nS# t          $ rF | t          j        v rt          j        |          cY S t                                          |           cY S w xY w	  ||           }n4# |$ r  t          $ r" t          j                            |           }Y nw xY w||S t                      }|                    |           S )u  Get a value from ``os.environ`` or ``~/.hermes/.env``, scope-aware.

    The ``os.environ`` read routes through ``agent.secret_scope.get_secret``
    so that, under an active profile scope (multiplexed gateway turn), this
    is scope-checked rather than leaking another profile's raw ``os.environ``
    value. ``get_secret`` encodes the whole policy: global vars pass through;
    scope is authoritative under multiplexing (miss -> None, no environ
    fallthrough); when multiplexing is off it behaves exactly like the
    legacy ``os.environ`` read. Its siblings ``get_env_value_prefer_dotenv``
    and ``gateway.config._getenv`` already work this way — this was the last
    scope-blind reader of the trio (#67027).
    r   UnscopedSecretError
get_secret)agent.secret_scoper  r  r   r   r;  r2  r   )r9   r  _get_secretr>  r/  s        r%   r  r    s   #	
 	
 	
 	
 	
 	
 	
 	
 	
  # # #"*:c?"""zz~~c"""""#
"k#    " " "jnnS!!"

 zzH<<s&    *A!AAA+ +.BBc                 B   t                      }|                    |           }|r|S 	 ddlm}m} n/# t
          $ r" t          j                            |           cY S w xY w	  ||           S # |$ r  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   r  )r2  r   r  r  r  r   r   r;  )r9   r/  r>  r  r  s        r%   get_env_value_prefer_dotenvr  2  s     zzH
,,s

C
 
#	
 	
 	
 	
 	
 	
 	
 	
 	
  # # #z~~c"""""##{3    # # #z~~c"""""#s!   2 )AA"
A- -.BB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)r9   r  s     r%   
redact_keyr  X  s7     )(((((;s%VZ"@"@AAAAr_   >   jwtr9   authtokenapikeybearerpasswdsecretr  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  c                 6    g | ]}t          |d z             S )r  )redact_config_value)r:  r<  r  s     r%   r  z'redact_config_value.<locals>.<listcomp>  s(    BBBq#Avz22BBBr_   )
r  r  r  r  r  r0   r   _SECRET_CONFIG_KEYSr  r   )rD  r  r  rv  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Lr_   c                  8   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/d0          }|                    d1d0          }t          d2| d3| d4           t                       t          t          d5t          j        t          j                             |                     d6i           }t          d7|                    d8d9                      t          d:|                    d;d<                      t          d=|                    d>d?           d@           |                    d8          dAk    r(t          dB|                    dCdD                      n|                    d8          dEk    r(t          dF|                    dGdH                      n|                    d8          dIk    rMt          dJ|                    dKdD                      t#          dL          }t          dM|rdNndO            nT|                    d8          dPk    rLt          dQ|                    dRdD                      t#          dS          }t          dT|rdNndO            n|                    d8          dUk    rwt          dV|                    dWdX                      t          dYt#          dZ          s-t#          d[          r t#          d\          rt#          d]          rdNndO            n_|                    d8          d^k    rFt#          d_          }t#          d`          }t          da|pdO            t          db|pdO            t                       t          t          dct          j        t          j                             |                     ddde          }|rt          df|            n*t          dft          dgt          j                              t                       t          t          dht          j        t          j                             |                     dii           }|                    djd(          }t          dk|rdlndm            |rt          dn|                    dodp          dqz  drds           |                    dt          }|B	 t?          |          }|dk    rt          du|dvdw           n# t@          tB          f$ r Y nw xY wt          dx|                    dydz          dqz  drd{           t          d||                    d}d~           d           t          d|                    dd           d           |                     di                               dii           }|                    dde          pd}t          d|            |                    dd          }|r|dk    rt          d|            |                     di           }|                    di           |                    di           d}tE          d |#                                D                       }|rt                       t          t          dt          j        t          j                             |$                                D ]\  } }!|!                    dd          }"|!                    dde          }#|"dk    s|#rId|" g}$|#r|$%                    d|#            t          d| ddd	                    |$                      t                       t          t          dt          j        t          j                             t#          d          }%t#          d          }&t          d|%rdNnt          dt          j                              t          d|&rdNnt          dt          j                              	 ddl&m'}'m(}(  |'            })|)r |(|)          }*t                       t          t          dt          j        t          j                             |)D ]}+|+d         },|*                    |,de          }|+                    dde          }-|rt3          |          nt          dOt          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   r  u7     ⚷ Some settings are managed by your administrator (z) and cannot be changedz    Managed config keys: r5  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 setry  rz  z  Max turns:    HERMES_MAX_ITERATIONSNu9                   ⚠ .env has stale HERMES_MAX_ITERATIONS=z& (run 'hermes doctor --fix' to remove)u   ◆ Displaydisplayz  Personality:  personalitynonez  Reasoning:    show_reasoningTrp  ro  z  Bell:         bell_on_completeFuser_message_previewfirst_linesr  
last_linesz  User preview: first z line(s), last z line(s)u   ◆ Terminalr  z  Backend:      r6  localz  Working dir:  r  r.   z  Timeout:      r  <   sr   z  Docker image: r  z*nikolaik/python-nodejs:python3.11-nodejs20singularityz  Image:        r  z3docker://nikolaik/python-nodejs:python3.11-nodejs20modalz  Modal image:  r  MODAL_TOKEN_IDz  Modal token:  
configuredr  daytonaz  Daytona image: r  DAYTONA_API_KEYz  API key:      vercel_sandboxz  Vercel runtime: r  node24z  Vercel auth:    VERCEL_OIDC_TOKENVERCEL_TOKENVERCEL_PROJECT_IDVERCEL_TEAM_IDsshr  r  z  SSH host:     z  SSH user:     u   ◆ Timezonetimezoner   z  Timezone:     z(server-local)u   ◆ Context Compressioncompressionr  z  Enabled:      r   rn  z  Threshold:    	thresholdg      ?d   z.0f%threshold_tokensz  Token cap:    r4  z* tokens (takes lower of ratio vs absolute)z  Target ratio: target_ratiog?z% of threshold preservedz  Protect last: protect_last_nr  z	 messagesz  Protect first: protect_first_nr  z non-system head messages	auxiliaryz(auto)r+  r  z  Provider:     visionweb_extract)VisionzWeb extractc              3   v   K   | ]4}|                     d d          dk    p|                     dd          V  5dS )r+  r  r)  r   Nr  )r:  ts     r%   r  zshow_config.<locals>.<genexpr>?  s\         	
j&!!V+AquuWb/A/A     r_   u    ◆ Auxiliary Models (overrides)z	provider=zmodel=12su   ◆ Messaging Platformsr  r  z  Telegram:     znot configuredz  Discord:      )r  resolve_skill_config_valuesu   ◆ Skill Settingsr9   r  z<20s[]u   ────────────────────────────────────────────────────────────z+  hermes config edit     # Edit config filez!  hermes config set <key> <value>z+  hermes setup           # Run setup wizard))r  r/  r   r   CYANr  r  r  load_managed_envr  YELLOWBOLDr@  rA  rL  rO  r   r  r  hermes_cli.authr  r  r   r  r2  r0   r   r   r  r  r  rW  r  r]   ro  rM  r  r  r  r  r  )/r  r  _managed_keys_managed_env_managed_dirr?  env_keyr   rD  r  anthropic_value_cfg_max_turns
_env_ghostr  ump	ump_firstump_lastr  modal_tokendaytona_keyr  r  tzr  r  _tt	_aux_comp_smcomp_providerr  	aux_taskshas_overrideslabeltask_cfgprovmdlr  telegram_tokendiscord_tokenr  r  
skill_varsr  r  r9   r"  display_vals/                                                  r%   show_configr    sk   ]]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	
UW[[1A4%H%HSTTe
U
UVVV	
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	 	 $4	4	4M8<<0@(#K#KMMNNN  W=AT3U3U  $UZghvZwZw  $U  }J  K^  }_  }_  $U  dq  rB  dC  dC  $U<<  JU  W  W  	X  	X  	X  	X	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oo011?#hh77^S^^^^___z*   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	GGGGGs8    BQ 
Q Q )i iiC8y 
yy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)r  zCreated r@   rA   r   rR  )notepadcodevimvinano)r  r  r  r  r  z#No editor found. Config file is at:r  zOpening  in z...)r   r0  rL  r   r  r  r/  r   r   r   r5   rV  which
subprocessrunr0   )r   editorr   _sys
candidatescmds         r%   edit_configr  r  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,,-.....r_   c                     t          | pd                                                                          }|dv rdS |dv rdS dS )zBReturn the cron drift guard axis affected by a config key, if any.r   >   
model.namemodel.modelr)  model.defaultr)  >   model.providerr+  r+  N)r0   r   r   )r9   r   s     r%   %_cron_model_drift_axis_for_config_keyr#    sP    SYB%%''--//JLLLw333z4r_   c                     | !	 t                      } n# t          $ r Y dS w xY wt          | t                    sdS |                     d          }t          |t                    sdS |                    dd          duS )ab  Return whether cron must fail closed on unpinned inference drift.

    Only the literal YAML boolean ``false`` disables this spend-safety guard.
    Missing, malformed, or non-boolean values stay fail-closed. When *config*
    is omitted, load the active merged configuration so CLI warnings honor the
    same user/managed setting as the scheduler.
    NTr  model_drift_guardF)r  r   r  r  r   )r  cron_configs     r%   cron_model_drift_guard_enabledr'    s     ~	 ]]FF 	 	 	44	fd## t**V$$Kk4(( t??.55UBB    
!!axisc                 x   |!	 t                      }n# t          $ r Y dS w xY wt          |t                    sdS |                    d          }t          |t                    sdS | dk    rdnd}|                    |          }t          |t
                    o t          |                                          S )a  True when cron.model / cron.model_provider covers *axis*.

    An axis covered by the explicit cron-fleet default no longer follows the
    global model/provider at fire time, so the drift guard never engages for
    it and switch-time warnings would be false alarms.
    NFr  r)  model_provider)r  r   r  r  r   r0   r  r   )r)  r  r&  r9   rD  s        r%   _cron_fleet_default_covers_axisr,    s     ~	 ]]FF 	 	 	55	fd## u**V$$Kk4(( uW__''*:COOC  EeS!!9d5;;==&9&99r(  c                  H    	 ddl m}   |             S # t          $ r g cY S w xY w)a!  Best-effort read of the active profile's cron jobs database.

    Delegates to ``cron.jobs.load_jobs`` to reuse its BOM handling, corruption
    repair, and context-local store resolution (tests, embedders). Falls back
    to an empty list on any failure so config writes never break.
    r   	load_jobs)	cron.jobsr/  r   r.  s    r%   "_load_cron_jobs_for_config_warningr1    sI    ''''''y{{   			s    !!c                     t          |           }|dS t          |          sdS t          ||          rdS t          |pd                                                                          }|sdS |}| d}d}t                      D ]}|                    dd          s|                    d          r/t          |                    |          pd                                          rft          |                    |          pd                                                                          }	|	r|	|k    r|dz  }|dk    rdS |dk    rd	nd
}
|dk    rdnd}t          d| d|
 d| d| d| d           dS )a]  Warn when a global model/provider change will trip cron's drift guard.

    Cron intentionally fails closed when an unpinned agent job's current global
    model/provider differs from its creation-time snapshot. Surface that outcome
    when the operator changes the global axis instead of letting the next tick
    be the first visible signal.
    Nr   	_snapshotr   r  Tno_agentr  jobjobshashaveu   ⚠️  z enabled unpinned cron r   z stored z( values that differ from the new global z. They will fail closed on their next run instead of silently using the changed model/provider. Inspect with `hermes cron list`, then pin the intended values with `cronjob action=update job_id=<job_id> provider=<provider> model=<model>`.)	r#  r'  r,  r0   r   r   r1  r   r/  )r9   rD  r  r)  	new_valuepinned_fieldsnapshot_fieldaffectedr5  rO  nounverbs               r%   1warn_unpinned_cron_jobs_after_model_config_changer?    s    155D|)&11 
 'tV44 EKR  &&((..00I L'''NH133 	 	wwy$'' 	77: 	sww|$$*++1133 	sww~..4"55;;==CCEE 	I--MH1}}MM55vDMM55vD		.8 	. 	.D 	. 	.4 	. 	.	. 	.CG	. 	. 	.    r_   c                     t           }|                     d          D ]&}t          |t                    r||vr dS ||         }'t          |t                    s|ndS )zReturn the leaf value declared for *dotted_key* in ``DEFAULT_CONFIG``.

    Unknown keys and non-leaf paths return ``None`` so they retain the legacy
    best-effort coercion used by ``config set``.
    r.   N)r  r  r  r  )r  r3  r  s      r%   _default_value_for_keyrA    sm     D  %%  $%% 	T)9)944Dz!$--74447r_   >   goalsr  secretsrj  r  model_catalogpersonalitiesquick_commandsserver_actionschannel_promptscommand_allowlistcredential_pool_strategies>   smsemailqqbotslackwecomfeishumatrixr  weixindiscordyuanbaodingtalkr  telegramwhatsapp
mattermostbluebubblescheckpointsr  c                      t          t          j                              } |                     t                     |                     t
                     |                     t                     | S )a.  Return the union of known top-level config keys for validation.

    Combines :data:`DEFAULT_CONFIG` with the dynamic categories that
    accept user-supplied child keys.  Used by :func:`_validate_config_key`
    to decide whether a ``hermes config set`` invocation is targeting a
    known shape.
    )r>  r  r?  r  _OPEN_DICT_TOP_LEVEL_KEYS_DYNAMIC_TOP_LEVEL_KEYS_SCHEMA_DEFINED_DICT_KEYS)r?  s    r%   _known_top_level_keysr_  Z  sX     ~"$$%%DKK)***KK'(((KK)***Kr_   333333?r  cutoffc                 n    ddl }|                    | t          |          d|          }|r|d         ndS )u  Return the closest valid key name from ``candidates`` if any are
    similar enough to ``key``, else None.  Used by ``hermes config set``
    to point users at the right path when they've typo'd a top-level key.

    Uses :func:`difflib.get_close_matches` with a conservative cutoff so
    we only suggest when there's a strong match — we'd rather say nothing
    than mislead a user toward a wrong-but-similar key.
    r   Nr  )r  ra  )difflibget_close_matchesrA  )r9   r  ra  rc  matchess        r%   _suggest_closest_keyrf  i  sD     NNN''VJ-?-?1V'TTG *71::d*r_   c                    | sdS |                      d          }|d         }|                    d          rdS t                      }|t          v rdS ||vr@t	          ||          }|,d                    |dd                   }|r| d| n|}d|fS dS |t          v s|t          v s	|t          v rdS t          j
        |          }|g}|dd         D ]}	|	t          v r dS t          |t                    s dS |	|vrSt	          |	t          |                                                    }
|
d                    ||
gz             }d|fc S  dS |                    |	           ||	         }dS )	a  Validate a dotted config-key path against the known schema.

    Returns ``(is_known, suggested_alternative_or_None)``.  Known keys
    return ``(True, None)``.  Unknown keys return ``(False, <suggestion>)``
    where ``<suggestion>`` may be ``None`` if no close match was found.

    Validates as deep as DEFAULT_CONFIG can be safely walked, then stops
    at any segment that hits an open-dict container (mcp_servers,
    providers, hooks, etc.) where users define the inner keys themselves.

    Headline case from #34067: ``gateway.discord.gateway_restart_notification``
    was silently written, even though ``gateway`` only has 4 known sub-keys
    (``strict``, ``media_delivery_allow_dirs``, ``trust_recent_files``,
    ``trust_recent_files_seconds``). The correct path is
    ``discord.gateway_restart_notification`` (platform configs live at the
    top level, not under a ``platforms`` namespace).
    )FNr.   r   rC  )TNNr  F)r  r	  r_  _PLATFORM_CONTAINER_KEYSrf  r@  r\  r]  r^  r  r   r  r  r>  r?  r  )r9   segmentstopknown
suggestionrestsuggested_fullr3  consumedsegsibling_suggestion
fixed_paths               r%   _validate_config_keyrs  w  s   $  {yy~~H
1+C ~~c z!##E &&&z
%)#u55
!88HQRRL))D7;K
33T333N.(({ '''32I+I+ISTmMmMm z"3''DuH|   ***::$%% 	
 ::d??!5c3tyy{{;K;K!L!L!- XXh2D1E&EFF
j((((;;Cy :r_   forcec           
         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           t          |           rGddlm}  ||                                 |           t          d|  dt                                  dS t          |           \  }}t!                      }	t#          |	           i }
|	                                r	 t'          |	d          5 }t)          |          pi }
ddd           n# 1 swxY w Y   nM# t*          $ r@}t          d|	 d| dt          j        
           t          j	        d           Y d}~nd}~ww xY w|}t-          t/          |           t0                    s|                                dv rd}nu|                                dv rd}n\|                                rt7          |          }n8|                    ddd                                          rt;          |          }|}|                                                                 }|                    d          r3|
                     d          }t-          |t0                    r	|rd|i|
d<   d| vr|
                     |           }t-          |tB                    r| dk    rH|r"t          dtE          |           d           nnd} t          d tE          |           d!           nJ|sGd" |D             }t          d#|  d$|  d%tE          |           d&t          j        
           |r}d'#                    |dd(                   }t          d)| t          j        
           tE          |          d(k    r/t          d*tE          |          d(z
   d+t          j        
           t          d,t          j        
           t          d-|  d.t          j        
           t          d/t          j        
           t          d0|  d1|t          j        
           t          j	        d           tI          |
| |           |                                                                 }|d2v r tK          |
          }
d3} t          d4           tM                       dd5l'm(}  ||	|
d6           tS          |           }|r#| d7k    rtU          |tW          |                     | d8k    rht-          |t0                    rS|rQ	 tY                      d9z  | d:z  }|                                r|-                                 n# t*          $ r Y nw xY w| .                    dd          d;                                         }|t^          v r)t-          |t0                    r|rdd<l0m1}  ||          }n|}t          d|  d=| d|	            te          | ||
           |s|st          tg          d>|  d?th          j5                             |r*t          tg          d@| th          j5                             t          tg          dAth          j6                             dS dS dS )Bu  Set a configuration value.

    Args:
        key: Dotted config path (e.g. ``terminal.backend``).
        value: String value (auto-coerced to bool/int/float when matching).
        force: When True, skip the unknown-key warning — useful for scripted
            writes of keys the running version doesn't recognize yet — AND
            authorize destructive replacement of a mapping section by a
            scalar (e.g. ``--force model gpt-x`` replaces the whole ``model:``
            mapping). Without --force, scalar writes over mapping sections are
            refused (bare ``model`` is redirected to ``model.default``). The
            CLI exposes this via ``hermes config set --force``.
    zset configuration valuesNr   r  rJ  rg  zCannot set '(': it is managed by your administrator (A) and cannot be changed. Contact your administrator to modify it.r-  r  r  u   ✓ Set r  r   r      ✗ Cannot parse r-   
  The file contains a YAML syntax error. Fix the error
  in your config file first, then retry.
  (hermes config edit will open it in your editor.)>   rp  r   r   T>   rn  ro  r  Fr.   r   zmodel.r)  r  u?   ⚠ Replacing entire 'model' section with a scalar (discarding z existing sub-key(s))r!  u<   ✓ Redirecting bare 'model' to 'model.default' (preserving z existing model sub-key(s))c                 <    g | ]}t          |t                    |S r   )r  r0   )r:  r;  s     r%   r  z$set_config_value.<locals>.<listcomp>W  s'    CCCa
1c0B0BCCCCr_   u   ✗ Cannot set 'u   ' to a scalar — 'z"' is a configuration section with z sub-key(s).r5  rf  z  Sub-keys: z
  ... and z morez/  Use a dotted path to set a specific leaf key:z    hermes config set z.<sub-key> <value>z/  Or use --force to replace the entire section:z    hermes config set --force r   )zmodel.api_baserm  zmodel.base_urlu<     (note: 'api_base' is an alias — saved as model.base_url)r  r  terminal.cwdzdisplay.skinskinsz.yamlr^  r  r  u   ⚠ 'uU   ' is not a recognized config key — it was saved anyway, but Hermes may not read it.z  Did you mean: z  (Custom top-level keys are supported and bridged to the environment for skills/external tools. Use --force to skip this notice.))7r   r0  r  r  is_key_managedr  r/  r5   r6   exitr  r  r  r  rO  rs  rL  r  r   r<  rH  r   r  rA  r0   r   isdigitrW  r  rG  r   r	  r   r  r  r@  r  rw  r  r  r  r  r  r  r   touchrsplitr  r  r  r?  r   r   r  r  )r9   rD  rt  r  r  rp  r  is_knownrl  r   r  rA  r*   coerced_value
_model_key
_model_val	_existing_sub	_sub_list_alias_normr  r  	skin_file	_leaf_keyr  _display_values                             r%   set_config_valuer    s    || 0111 )(((((##C(( #3355/:S{]**@SO3 O O O O O	
 	
 	
 	

 	#  	QPPPPP$$SYY[[%888222,..22333 044Hj
 "##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 	 	 	GK G G3 G G G Z    HQKKKKKKKK	$ M,S11377 );;==111 MM[[]]444!MM]]__ 	)JJMM]]3A&&..00 	)!%LLME ""$$JX&& ; __W--
j#&& 	;: 	;$-z#:K  #~~OOC((	i&& 0	g~~ M'*9~~M M M    *CS'*9~~S S S   
  CC9CCCJs J Js J J25d))J J J   
   $		$rr( 3 3I4443:FFFF4yy1}}=TQ===!$    E    DSDDD    E    DSDD5DD    S%(((
 ))++##%%K4440==LMMM''''''k;%@@@@ .c22G <3.((w 3E : :;;; nE3!7!7E	'))G3oooEI!! "!!! 	 	 	D	 

3""2&,,..I'''Juc,B,B'u',,,,,,$U++	
>S
>
>^
>
>
>
>???5c5+NNN  E e*C * * *M
 
 	 	 	
  	I%7:77GGHHHe J	
 
 	 	 	 	 	   sN   *E% ;EE% EE%  E!E% %
F//6F**F/+?V+ +
V87V8r  c                h   t          |           r-t          |                                           }|t          n|}nt	          t                      |           }|t          u r2t          d|  t          j                   t          j	        d           t          t          ||                     dS )z%Print a resolved configuration value.NConfig key not set: r-  r  r  )r  r  r  r  r  r  r/  r5   r6   r~  r  )r9   r  	env_valuerD  s       r%   get_config_valuer    s    # 0!#))++..	%-9KMM3//*S**<<<<	
"5'
:
:
:;;;;;r_   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           t          |           rddlm}  ||                                                               d          s2t          d|  t          j        
           t          j	        d           t          d|  dt                                  dS t!                      }t#          |           i }|                                r	 t'          |d          5 }t)          |          pi }ddd           n# 1 swxY w Y   nM# t*          $ r@}t          d| d| dt          j        
           t          j	        d           Y d}~nd}~ww xY wt-          ||           }	t/          |           }
|
r| dk    rt1          |
          p|	}	|	s2t          d|  t          j        
           t          j	        d           t3                       ddlm}  |||d           t          d|  d|            dS )z.Remove a user-set configuration or .env value.zunset configuration valuesNr   r  rJ  rg  zCannot unset 'rv  rw  r-  r  )remove_provider_env_credentialrr  r  u
   ✓ Unset z from r   r   rx  r-   ry  r{  r  Fr  )r   r0  r  r  r}  r  r/  r5   r6   r~  r  r  r  r  r   rO  rL  r  r   r<  rH  r   r  r  r}  r  r  r  )r9   r  r  rp  r  r   r  rA  r*   r  r  r  s               r%   unset_config_valuer    se   || 2333 )(((((##C(( #3355/:S{]**@SOS O O# O O O	
 	
 	
 	

 	# 
 	SRRRRR--ciikk::>>wGG 	...SZ@@@@HQKKK6366lnn66777!##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 	 	 	GK G G3 G G G Z    HQKKKKKKKK	 K--G .c22G 73.(("7++6w *S**<<<<''''''k;%@@@@	
/s
/
/+
/
/00000s<   F -F?F FF FF 
G!!6GG!c           	      B   t          | dd          }||dk    rt                       dS |dk    rt                       dS |dk    rt          | dd          }|smt          d           t                       t          d           t          d	           t          d
           t          d           t	          j        d           t          |t          | dd                     dS |dk    rt          | dd          }t          | dd          }t          t          | dd                    }|r|t          d           t                       t          d           t          d           t          d           t          d           t                       t          d           t          d           t	          j        d           t          |||           dS |dk    rt          | dd          }|smt          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)          |
           d0           |
D ]W}|                    d1g           }|r!d2d3                    |dd4                    d5nd6}t          d-|d.          |            Xt                       t/          d7d8          }t                       |d9         s|d:         r't          t          d;t          j                             |d<         rCt                       |d<         D ],}t          t          d=| t          j                             -t                       dS |d>k    r~t                       t          t          d?t          j        t          j                             t                       t%                      \  }}||k    rt          d%| d@           n.t          t          d%| d&| dAt          j                             t                       t          t          dBt          j                             t2          D ]O}t5          |          rt          dC|            $t          t          dD| dEt          j                             Pt                       t          t          dFt          j                             t9          j                    D ]\  }}t5          |          rt          dC|            '|                    d1g           }|r d&d3                    |dd4                    nd6}t          t          dG| | t          j                             t#                      }|rUt                       t          t          dHt)          |           dIt          j                             t          dJ           t                       dS t          dK|            t                       t          dL           t          dM           t          dN           t          dO           t          dP           t          dQ           t          dR           t          dS           t          dT           t          dU           t	          j        d           dS )VzHandle config subcommands.config_commandNshoweditr   r9   z'Usage: hermes config get <key> [--json]z	Examples:z  hermes config get modelz$  hermes config get terminal.backendz(  hermes config get skills.config --jsonr  r  Fr  r>  rD  rt  z0Usage: hermes config set [--force] <key> <value>z3  hermes config set model anthropic/claude-sonnet-4z+  hermes config set terminal.backend dockerz0  hermes config set OPENROUTER_API_KEY sk-or-...z=  --force: skip the unknown-key notice for unrecognized keys,z@           and allow a scalar to replace a whole mapping section)rt  unsetz Usage: hermes config unset <key>z  hermes config unset modelz&  hermes config unset terminal.backendz(  hermes config unset OPENROUTER_API_KEYrb  zenv-pathmigrateu*   🔄 Checking configuration for updates...r  u    ✓ Configuration is up to date!z  Config version: r  r  z1 new config option(s) will be added with defaultsc                 <    g | ]}|                     d           |S )r  r  r  s     r%   r  z"config_command.<locals>.<listcomp>Z  s)    KKK!aeeM6J6JKAKKKr_   c                 f    g | ].}|                     d           |                     d          ,|/S )r  r  r  r  s     r%   r  z"config_command.<locals>.<listcomp>[  sN     
 
 
55''
01j0A0A

 
 
r_   u   
  ⚠️  z required API key(s) missing:u	        • r   u   
  ℹ️  z$ optional API key(s) not configured:toolsz (enables: r5  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 editorz@  hermes config get <key>          Print a resolved config valuez6  hermes config set <key> <value>   Set a config valuez8  hermes config unset <key>        Remove 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)getattrr  r  r/  r5   r~  r  r  r  r  rL  rO  r   r   r  r  r  r  r  GREENr  r   r@  r$  r  r  r  REDr  r  r  )r  subcmdr9   rD  rt  r  r   r  r  required_missingoptional_missingr  r  	tools_strr  r4   r  r@  s                     r%   r  r    s   T+T22F~6))	6			5dE4(( 	;<<<GGG+-...8999<===HQKKKgdFE&B&BCCCCCC	5dE4((gt,,WT7E2233 
	emDEEEGGG+GHHH?@@@DEEEGGGQRRRTUUUHQKKKe5111111	7		dE4(( 	4555GGG+/000:;;;<===HQKKK3	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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?@@@PQQQFGGGHIIIKLLLHIII?@@@=>>>r_   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  r  r(  r  categoryr  )_profile_env_vars_injectedrj  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  r  r  zplugin.yamlz
plugin.ymlr3  r   r   r  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>'  sE       $ $ #++C00$ $ $ $ $ $r_   )r  r  _KEY	_PASSWORD_JSONr  z configurationr  r(  r  	messaging)r  r  r(  r  r  )"_platform_plugin_env_vars_injectedr  r   r   r   r  r  iterdirr   r<  rH  r   r   r   r   r  r  r0   r  r  r  r  ro  )r  	repo_rootplatforms_dirr_  manifest_pathrA  manifestr  entriesr  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)FrS  )TF)r   )r`  )r  r'  r  r  r   rV  rC  r   r   r  r5   rC  	threadingr   dataclassesr   pathlibr   r  r   r   r   r   r	   r
   hermes_cli.route_identityr   hermes_cli.secret_promptr   r	  r  r3   r>  r   r  r&   r   r0   r;   system_IS_WINDOWScompilerm  	frozensetr[   r^   r`   ra   rW  rb   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*  r0  r  rE  r  r   rF  r  rG  rH  rL  rO  r   tupler\  rc  rj  rs  ru  rz  r{  r  r  hermes_cli.config_defaultsr  r  r  r  r  r  r  objectr  r  r  r  r  r  r  r  r  rX  r\  ra  rk  rr  ry  r~  rH  r  r  r  r  r  r  _EXTRA_KNOWN_ROOT_KEYSr?  r  _VALID_CUSTOM_PROVIDER_FIELDSr  r  r  r  r  r  r$  r+  r)  r5  Matchr?  rB  rG  rN  rR  rU  rb  rk  rw  r~  r_  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  _COMMENTED_SECTIONSr  r   r2  r'  rG  r4  r   r)  r   r[  rb  re  r  ry  r}  r  r  r  r  r  r  r  r  r  r  r  r  r#  r'  r,  r1  r?  rA  r\  r^  r]  rh  r_  rf  rs  r  r  r  r  r  r  r  r  r   r_   r%   <module>r     s         				  				       



       ! ! ! ! ! !       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n ;E8 8 88%8478	8 8 8 8t 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   ) @ @ @ @ @B  + + + + + + + + L L L L L L L L ,    T,

 $)VZ$899 HSM    ",D , , , ,s HSM    2 2x~ 2 2 2 2 2c c c# c c c cLt      HTN d    *# #    9C 9 9 9 90K 6"c " " " " 3 s    &; ;# ; ; ; ;*$ * * * *d F E E E E E E E 0 0 0 0 0 0 0 0- - - - -&d & & & &2$ 2 2 2 2x}hsm'C!D    <   :  @t    2  $$ 4    0  CEE c ! ! !/" /" /"d"d " " " "4 I H H H H H H H YXX "67E E E		- - T#tCy.)       d38n9M    ,0C 0 0 0 0l    CH~  	
  
#s(^   6 688C    $:c :d : : : :zC D    .     4S#X#7    8,tDcN'; , , , ,j #&#%% C ' ' '"%,/8;	    ~ ~ ~~ ~ d38n	~ ~ ~ ~H $ $ $$ $ d38n	$ $ $ $Ns tDcN?S    " (,/ /T#s(^$/	$sCx./ / / /dc htn      8<'+ tDcN34 T#s(^$ 
#s(^	   F 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(^	! ! ! !N 8<'+	. .S>.. tDcN34. T#s(^$	.
 
. . . .4 8<'+	> >>> tDcN34> T#s(^$	>
 c]> > > >B# #    >$ > > > >&eCHo    R   6 90^022336LL ! ! !   WVV         R RhtCH~&> R$}J] R R R Rj0 0(4S>": 0d 0 0 0 0,,4 ,4$sCx.)A ,4T ,4 ,4 ,4 ,4^tCH~ $    4G G GD GT#s(^ G G G GTT T d    &d d t    8D s uT3Y7G    .-& -&c -& -& -& -&`	3 	8C= 	 	 	 	  "   >  = = = =@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>    >htCH~&> 4    2 GK + + +$sCx.) +# + +s + + + +^#c3h # # # #L.2 .2htn .2S#X .2 .2 .2 .2b*$sCx. * * * *Z 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#$ 0;/'%M/51A'Q;)3=   Ds s    : :# : : : : %)'+#	1 1 1	$sCx.	!1 T#s(^$1 tn	1
 
#s(^1 1 1 1hP Pc3h P P P Pf $ 2 F  48 q\ q\ q\cNq\ q\ Cc3h01	q\
 q\ q\ q\ q\h     .8$sCx. 8 8 8 8@ [_
HU5huox}!DEtCQTH~UVW ^ ^ ^	 	 	 	t     ,*3 * * * *Z%S % % % % % %PC C    ** *# *$ * * * *[ [C [ [ [ [|Pc Pc P P P P&F# F$ F F F FR$ $c $ $ $ $$ $ $ $" "# " " " "s 3 4S>    C    ,"s "x} " " " "J#S #Xc] # # # #LBC BC B B B B  i ! ! !   ( s C     :W W Wt%/ %/ %/Ps x}     (,C CT#s(^$C	C C C C2 (,: :
:T#s(^$: 
: : : :2Dc3h,@    " (,6 6	66 T#s(^$6 
	6 6 6 6r8s 8 8 8 8& &I ' ' '   & &I ' ' '    $)%    %9k]33 s3x    + +c +s3x + +QYZ]Q^ + + + +]c ]eD(3-,?&@ ] ] ] ]@] ]# ]c ]$ ] ] ] ]@ 38 < < <# <4 < < < <>1C >1 >1 >1 >1Jd d dX #    >     . &+ "C C C CN !   " " " " "r_   