
    #jT^                        U d 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m	Z	m
Z
mZmZmZ ddlmZmZmZ  ej        e          Zdddd	Z ed
          ZdefdZdadefdZdedee	eef         ef         fdZde	eef         defdZ eh d          Zi Z e	eef         e!d<   dedefdZ"de	eef         defdZ#d0dedz  dee         fdZ$dee         fdZ%i Z&e	eee'f         e
e         f         e!d<   d1dZ(de
e         fdZ)de
e         fdZ*de	eef         de	ee
f         fdZ+de	eef         de
e	eef                  fdZ,de
e	eef                  fd Z-d!Z.d"e	eef         d#efd$Z/d%e
e	eef                  de	eef         fd&Z0de	eef         defd'Z1d(ed)efd*Z2 ej3        d+          Z4d,edeee         ef         fd-Z5d.ee         defd/Z6dS )2a  Lightweight skill metadata utilities shared by prompt_builder and skills_tool.

This module intentionally avoids importing the tool registry, CLI config, or any
heavy dependency chain.  It is safe to import at module level without triggering
tool registration or provider resolution.
    N)Path)AnyDictListOptionalSetTuple)get_config_pathget_skills_dir	is_termuxdarwinlinuxwin32)macosr   windows)z.gitz.githubz.hubz.archivez.venvvenvnode_moduleszsite-packages__pycache__z.toxz.noxz.pytest_cachez.mypy_cachez.ruff_cachereturnc                     	 | j         }n3# t          $ r& ddlm}  |t	          |                     j         }Y nw xY wt          d |D                       S )aE  True if any component of *path* is in EXCLUDED_SKILL_DIRS.

    Use this on every SKILL.md path produced by ``rglob`` to prune
    dependency, virtualenv, VCS, and cache directories. Centralising the
    check here keeps every skill-scanning site in sync with the shared
    exclusion set.

    Accepts a Path or string.
    r   )PurePathc              3   (   K   | ]}|t           v V  d S NEXCLUDED_SKILL_DIRS).0parts     6/home/ubuntu/.hermes/hermes-agent/agent/skill_utils.py	<genexpr>z)is_excluded_skill_path.<locals>.<genexpr>>   s(      ==tt**======    )partsAttributeErrorpathlibr   strany)pathr!   r   s      r   is_excluded_skill_pathr'   /   sx    *
 * * *$$$$$$T##)* ==u======s   
 -::contentc                     t           ,ddlt          dd          pj        dt          ffd}|a t          |           S )z7Parse YAML with lazy import and CSafeLoader preference.Nr   CSafeLoadervaluec                 2                         |           S )N)Loader)load)r+   loaderyamls    r   _loadzyaml_load.<locals>._loadN   s    99U69222r    )_yaml_load_fnr0   getattr
SafeLoaderr$   )r(   r1   r/   r0   s     @@r   	yaml_loadr5   F   sl     }d33Ft	3 	3 	3 	3 	3 	3 	3 	3 !!!r    c                 X   i }| }|                      d          s||fS t          j        d| dd                   }|s||fS | d|                                dz            }| |                                dz   d         }	 t          |          }t          |t                    r|}n# t          $ rt |	                                
                    d          D ]I}d|vr|
                    dd          \  }}|	                                ||	                                <   JY nw xY w||fS )zParse YAML frontmatter from a markdown string.

    Uses yaml with CSafeLoader for full YAML support (nested metadata, lists)
    with a fallback to simple key:value splitting for robustness.

    Returns:
        (frontmatter_dict, remaining_body)
    z---z
\n---\s*\n   N
:   )
startswithresearchstartendr5   
isinstancedict	Exceptionstripsplit)	r(   frontmatterbody	end_matchyaml_contentparsedlinekeyr+   s	            r   parse_frontmatterrL   X   s[    #%KDe$$ !D  	-55I !D  1y001445L9==??Q&(()D
5<((fd## 	! K 5 5 5 &&((..t44 	5 	5D$C++JC',{{}}K		$$		5 	55 s    &B' 'A;D%$D%rE   c                    |                      d          }|sdS t          |t                    s|g}t          j        }t                      }|D ]|}t          |                                                                          }t                               ||          }|
                    |          r dS |r	|dk    r dS |r|dv r dS }dS )av  Return True when the skill is compatible with the current OS.

    Skills declare platform requirements via a top-level ``platforms`` list
    in their YAML frontmatter::

        platforms: [macos]          # macOS only
        platforms: [macos, linux]   # macOS and Linux

    If the field is absent or empty the skill is compatible with **all**
    platforms (backward-compatible default).

    Termux note: on Termux/Android, ``sys.platform`` is ``"linux"`` on
    older Pythons but became ``"android"`` on Python 3.13+. Termux is a
    Linux userland riding on the Android kernel, so skills tagged
    ``linux`` are treated as compatible in Termux regardless of which
    ``sys.platform`` value Python reports. Individual Linux commands
    inside a skill may still misbehave (no systemd, BusyBox utils, no
    apt/dnf, etc.) but that is on the skill, not on platform gating.
    	platformsTr   )termuxandroidF)getr@   listsysplatformr   r$   lowerrC   PLATFORM_MAPr;   )rE   rN   currentrunning_in_termuxrT   
normalizedmappeds          r   skill_matches_platformr[      s    ( ,,I ti&&  K	lG!  ]]((**0022
!!*j99f%% 	44
  	7!2!244 	+@!@!@445r    >   s6dockerkanban_ENV_DETECT_CACHEenvc                    | t           v rt           |          S d}| dk    r\t          j        d          st          j        d          rd}n	 ddlm} t           |                      }n# t          $ r d}Y nrw xY w| dk    r$	 dd	lm}  |            }nV# t          $ r d}Y nHw xY w| d
k    r>t          j	        
                    d          pt          j	        
                    d          }|t           | <   |S )zReturn True when the named runtime environment is currently active.

    Cached per process. Unknown env names return True (fail-open: never hide a
    skill because of a tag we don't understand).
    Tr^   HERMES_KANBAN_TASKHERMES_KANBAN_BOARDr   )_profile_has_kanban_toolsetFr]   )is_containerr\   z/run/s6z/package/admin/s6-overlay)r_   osgetenvtools.kanban_toolsrd   boolrB   hermes_constantsre   r&   isdir)r`   resultrd   re   s       r   _detect_environmentrm      sF     %%F
h 9)** 	bi8M.N.N 	FFJJJJJJ99;;<<   		555555!\^^FF 	 	 	FFF		
 y)) 
RW]]'.
 .
 $cMs$   A) )A87A8B B"!B"c                    |                      d          }|sdS t          |t                    s|g}|D ]V}t          |                                                                          }|s8|t          vr dS t          |          r dS WdS )uJ  Return True when the skill is relevant to the current runtime environment.

    Skills may declare an ``environments`` list in their YAML frontmatter::

        environments: [kanban]        # only relevant when kanban is active
        environments: [s6]            # only relevant inside the s6 Docker image
        environments: [docker]        # only relevant inside any container

    If the field is absent or empty the skill is relevant in **all**
    environments (backward-compatible default).

    This is an OFFER-time filter: it controls whether a skill shows up in the
    skills index / autocomplete / slash-command list. It is intentionally NOT
    enforced by ``skill_view`` or ``--skills`` preloading — an explicit load is
    explicit consent, and load-bearing force-loads (e.g. the kanban dispatcher
    injecting ``--skills kanban-worker``) must always succeed regardless of how
    the offer surfaces filter the skill.

    A skill matches when ANY of its declared environments is currently active
    (OR semantics, mirroring ``platforms``). Unknown env tags fail open.
    environmentsTF)rQ   r@   rR   r$   rU   rC   _KNOWN_ENVIRONMENTSrm   )rE   ro   r`   rY   s       r   skill_matches_environmentrq      s    , ??>22L tlD)) &$~  XX^^%%++--
 	00044z** 	44	5r    rT   c                    t                      }|                                st                      S 	 t          |                    d                    }nA# t
          $ r4}t                              d||           t                      cY d}~S d}~ww xY wt          |t                    st                      S |
                    d          }t          |t                    st                      S ddlm} | pt          j        d          p
 |d	          }|r;|
                    d
          pi 
                    |          }|t          |          S t          |
                    d                    S )a  Read disabled skill names from config.yaml.

    Args:
        platform: Explicit platform name (e.g. ``"telegram"``).  When
            *None*, resolves from ``HERMES_PLATFORM`` or
            ``HERMES_SESSION_PLATFORM`` env vars.  Falls back to the
            global disabled list when no platform is determined.

    Reads the config file directly (no CLI config imports) to stay
    lightweight.
    utf-8encodingz"Could not read skill config %s: %sNskillsr   )get_session_envHERMES_PLATFORMHERMES_SESSION_PLATFORMplatform_disableddisabled)r
   existssetr5   	read_textrB   loggerdebugr@   rA   rQ   gateway.session_contextrw   rf   rg   _normalize_string_set)rT   config_pathrI   e
skills_cfgrw   resolved_platformrz   s           r   get_disabled_skill_namesr     s    "##K uu;00'0BBCC   9;JJJuu fd## uuH%%Jj$'' uu777777 	69&''	6?455 
  <'^^,?@@FBKK
 
 (():;;; 
!;!;<<<s   #A 
B )B	BBc                 j    | t                      S t          | t                    r| g} d | D             S )Nc                     h | ]D}t          |                                          #t          |                                          ES  )r$   rC   )r   vs     r   	<setcomp>z(_normalize_string_set.<locals>.<setcomp>B  s9    ===qc!ffllnn=CFFLLNN===r    )r}   r@   r$   )valuess    r   r   r   =  s=    ~uu&# ==F====r    _EXTERNAL_DIRS_CACHEc                  8    t                                            dS )u(   Test hook — drop the in-process cache.N)r   clearr   r    r   _external_dirs_cache_clearr   P  s         r    c                     t                      } |                                 sg S 	 |                                 }t          |           |j        f}n# t
          $ r d}Y nw xY w|+t                              |          }|t          |          S 	 t          | 
                    d                    }n# t          $ r g cY S w xY wt          |t                    sg S |                    d          }t          |t                    sg S |                    d          }|sg }|t          |          t          |<   |S t          |t                    r|g}t          |t                    sg S ddlm}  |            }	t!                                                      }
t%                      }g }|D ]}t          |                                          }|s't(          j                            t(          j                            |                    }t1          |          }|                                s|	|z                                  }n|                                }||
k    r||v r|                                r+|                    |           |                    |           t:                              d|           |t          |          t          |<   |S )	u3  Read ``skills.external_dirs`` from config.yaml and return validated paths.

    Each entry is expanded (``~`` and ``${VAR}``) and resolved to an absolute
    path.  Only directories that actually exist are returned.  Duplicates and
    paths that resolve to the local ``~/.hermes/skills/`` are silently skipped.

    Cached in-process, keyed on ``config.yaml`` mtime — the function is
    called once per skill during banner / tool-registry scans, and YAML
    parsing a non-trivial config dominates ``hermes`` cold-start time
    when the cache is absent.
    Nrs   rt   rv   external_dirsr   )get_hermes_homez0External skills dir does not exist, skipping: %s)r
   r|   statr$   st_mtime_nsOSErrorr   rQ   rR   r5   r~   rB   r@   rA   rj   r   r   resolver}   rC   rf   r&   
expanduser
expandvarsr   is_absoluteis_diraddappendr   r   )r   r   	cache_keycachedrI   r   raw_dirsrl   r   hermes_homelocal_skillsseenentryexpandedps                  r   get_external_skills_dirsr   U  s    "##K 	!!&)+&6&68H%I		   			 %)))44<<;00'0BBCC   			fd## 	H%%Jj$'' 	~~o..H  .26ll +(C   :h%% 	000000!/##K!##++--LeeDF P PE

  "" 	7%%bg&8&8&?&?@@NN}} 	q))++AA		A9988:: 	PHHQKKKMM!LLKQOOOO*.v,,Y'Ms#   *A A A #B5 5CCc                  f    t                      g} |                     t                                 | S )u   Return all skill directories: local ``~/.hermes/skills/`` first, then external.

    The local dir is always first (and always included even if it doesn't exist
    yet — callers handle that).  External dirs follow in config order.
    )r   extendr   )dirss    r   get_all_skills_dirsr     s0     DKK(**+++Kr    c                 d   |                      d          }t          |t                    si }|                     d          pi }t          |t                    si }|                     dg           |                     dg           |                     dg           |                     dg           dS )z>Extract conditional activation fields from parsed frontmatter.metadatahermesfallback_for_toolsetsrequires_toolsetsfallback_for_toolsrequires_tools)r   r   r   r   )rQ   r@   rA   )rE   r   r   s      r   extract_skill_conditionsr     s    z**Hh%% \\(##)rFfd## !',CR!H!H#ZZ(;R@@$jj)=rBB **%5r::	  r    c                    |                      d          }t          |t                    sg S |                     d          }t          |t                    sg S |                     d          }|sg S t          |t                    r|g}t          |t                    sg S g }t	                      }|D ]3}t          |t                    st          |                     dd                                                    }|r||v rUt          |                     dd                                                    }|s||d}	|                     d          }
|
|
|	d<   |                     d
          }t          |t
                    r,|                                r|                                |	d
<   n||	d
<   |                    |           |                    |	           5|S )a   Extract config variable declarations from parsed frontmatter.

    Skills declare config.yaml settings they need via::

        metadata:
          hermes:
            config:
              - key: wiki.path
                description: Path to the LLM Wiki knowledge base directory
                default: "~/wiki"
                prompt: Wiki directory path

    Returns a list of dicts with keys: ``key``, ``description``, ``default``,
    ``prompt``.  Invalid or incomplete entries are silently skipped.
    r   r   configrK    description)rK   r   defaultNprompt)	rQ   r@   rA   rR   r}   r$   rC   r   r   )rE   r   r   rawrl   r   itemrK   descr   r   prompt_texts               r   extract_skill_config_varsr     s     z**Hh%% 	\\(##Ffd## 	
**X

C 	#t ec4   	#%FD  $%% 	$((5"%%&&,,.. 	cTkk488M2..//5577 	!
 !
 ((9%%&E)hhx((k3'' 	#K,=,=,?,? 	#)//11E(OO"E(OeMr    c                  r   g } t                      }t                      }t                      D ]}|                                st	          |d          D ]}	 |                    d          }t          |          \  }}n# t          $ r Y 8w xY w|                    d          p|j	        j
        }t          |          |v rot          |          st          |          }	|	D ]N}
|
d         |vrBt          |          |
d<   |                     |
           |                    |
d                    O
| S )aY  Scan all enabled skills and collect their config variable declarations.

    Walks every skills directory, parses each SKILL.md frontmatter, and returns
    a deduplicated list of config var dicts.  Each dict also includes a
    ``skill`` key with the skill name for attribution.

    Disabled and platform-incompatible skills are excluded.
    zSKILL.mdrs   rt   namerK   skill)r}   r   r   r   iter_skill_index_filesr~   rL   rB   rQ   parentr   r$   r[   r   r   r   )all_vars	seen_keysr{   
skills_dir
skill_filer   rE   _
skill_nameconfig_varsvars              r   discover_all_skill_config_varsr     si    &(HUUI'))H)++ . .
  "" 	0ZHH 	. 	.J **G*<<!23!7!7QQ    %00JJ4E4JJ:(**)+66 3K@@K" . .u:Y..#&z??CLOOC(((MM#e*---	.	.( Os   (B
BBzskills.configr   
dotted_keyc                     |                     d          }| }|D ]&}t          |t                    r||v r	||         }$ dS |S )zPWalk a nested dict following a dotted key.  Returns None if any part is missing..N)rD   r@   rA   )r   r   r!   rW   r   s        r   _resolve_dotpathr   5  sZ    S!!EG  gt$$ 	dmGG44Nr    r   c                 z   t                      }i }|                                rL	 t          |                    d                    }t	          |t
                    r|}n# t          $ r Y nw xY wi }| D ]}|d         }t           d| }t          ||          }|)t	          |t                    r*|
                                s|                    dd          }t	          |t                    rDd|v sd	|v r<t          j                            t          j                            |                    }|||<   |S )
aV  Resolve current values for skill config vars from config.yaml.

    Skill config is stored under ``skills.config.<key>`` in config.yaml.
    Returns a dict mapping **logical** keys (as declared by skills) to their
    current values (or the declared default if the key isn't set).
    Path values are expanded via ``os.path.expanduser``.
    rs   rt   rK   r   Nr   r   ~z${)r
   r|   r5   r~   r@   rA   rB   SKILL_CONFIG_PREFIXr   r$   rC   rQ   rf   r&   r   r   )	r   r   r   rI   resolvedr   logical_keystorage_keyr+   s	            r   resolve_skill_config_valuesr   A  sR    "##KF 	{44g4FFGGF&$''   	 	 	D	  "H & &%j,<<{<< 55=Zs33=EKKMM=GGIr**E eS!! 	Bse||tu}}G&&rw'9'9%'@'@AAE %Os   :A! !
A.-A.c                     |                      dd          }|sdS t          |                                                              d          }t          |          dk    r|dd         dz   S |S )z8Extract a truncated description from parsed frontmatter.r   r   z'"<   N9   z...)rQ   r$   rC   len)rE   raw_descr   s      r   extract_skill_descriptionr   j  sn    }b11H rx==  &&u--D
4yy2~~CRCy5  Kr    r   filenamec              #       K   g }t          j         d          D ]@\  }}}d |D             |dd<   ||v r%|                    t          |          |z             At	          | fd          D ]}|V  dS )zWalk skills_dir yielding sorted paths matching *filename*.

    Excludes Hermes metadata, VCS, virtualenv/dependency, and cache
    directories so dependencies cannot register nested skills.
    T)followlinksc                 $    g | ]}|t           v|S r   r   )r   ds     r   
<listcomp>z*iter_skill_index_files.<locals>.<listcomp>  s#    CCCa/B&B&B1&B&B&Br    Nc                 H    t          |                                         S r   )r$   relative_to)r   r   s    r   <lambda>z(iter_skill_index_files.<locals>.<lambda>  s    c!--
2K2K.L.L r    )rK   )rf   walkr   r   sorted)r   r   matchesrootr   filesr&   s   `      r   r   r   x  s       GWZTBBB 2 2dECCdCCCQQQuNN4::0111w$L$L$L$LMMM  



 r    z^[a-zA-Z0-9_-]+$r   c                 X    d| vrd| fS t          |                     dd                    S )z~Split ``'namespace:skill-name'`` into ``(namespace, bare_name)``.

    Returns ``(None, name)`` when there is no ``':'``.
    r9   Nr:   )tuplerD   )r   s    r   parse_qualified_namer     s3    
 $TzC##$$$r    	candidatec                 X    | sdS t          t                              |                     S )zDCheck whether *candidate* is a valid namespace (``[a-zA-Z0-9_-]+``).F)ri   _NAMESPACE_REmatch)r   s    r   is_valid_namespacer     s+     u##I..///r    r   )r   N)7__doc__loggingrf   r<   rS   r#   r   typingr   r   r   r   r   r	   rj   r
   r   r   	getLogger__name__r   rV   	frozensetr   ri   r'   r2   r$   r5   rL   r[   rp   r_   __annotations__rm   rq   r   r   r   intr   r   r   r   r   r   r   r   r   r   r   compiler   r   r   r   r    r   <module>r      s      				 				 



       8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 G G G G G G G G G G		8	$	$
    i  (>D > > > >( "s " " " "$"s "uT#s(^S-@'A " " " "P)S#X )4 ) ) ) )l  i : : :;; %' 4T	? ' ' '+S +T + + + +\$4S> $d $ $ $ $T'= '=sTz '=SX '= '= '= '=T>SX > > > >  ;= d5c?DJ67 < < <! ! ! !
S$t* S S S SlT$Z    $sCx. T#t)_    (84S> 8d4S>>R 8 8 8 8v$T#s(^(< $ $ $ $T & 	T#s(^ 	 	 	 	 	#d38n%#	#s(^# # # #R4S> c    t s    " 
.//%s %uXc]C-?'@ % % % %0(3- 0D 0 0 0 0 0 0r    